orElse는 T객체 그 자체를, orElseGet은 람다식을 넘길 수 있다는 차이가 있다.
//orElse
UserStatistics one = statistics.stream().filter(UserStatistics::isHoldem).findFirst().orElse(new UserStatistics());
//orElseGet
UserStatistics one = statistics.stream().filter(UserStatistics::isHoldem).findFirst().orElseGet(() -> new UserStatistics());
또한 메모리/성능 상에서 차이도 있다.
orElse 는 값이 존재하여 else에 설정한 값을 반환하지 않게 될 때에도 이미 만들어 놓는다.
orElseGet은 값이 없어 진짜 필요할 때 실행된다.
따라서,
constant, static variable, or caching result 을 반환할 경우라면 orElse를 사용해도 되지만, 대량 작업을 하거나 새로운 객체를 생성해야할 경우 orElseGet를 사용하는게 좋겠다.
public static <T> void assertThat(T actual, Matcher<? super T> matcher)
TypeSafeMatcher를 상속받아서 매처 인스턴스를 반환하는 정적 팩토리 매서드(static @Factory method)를 제공하는 방식으로 커스텀 매쳐를 만들 수 있음(ConstrainsSidesTo.java, RectangleTest.java)
7.3.2 불변 함수를 내장하여 범위 테스트
sparseArray라는 자료구조를 직접 구현
내부 배열에 저장된 값을 검증하는 테스트 코드를 작성할 때
불필요하게 내부 구현 사항을 노출하기보다
검증 함수를 만들어서 활용하는게 낫다(테스트 코드에서도 쓰고, 필요시 로직에서도 쓰고; checkInvariants())
이 때 코드의 어느 부분에 문제가 있는지 더 쉽게 파악 가능
cf. sparseArray
0이 데이터보다 훨씬 많이 있는 array. 0을 실제 저장하지 않고 데이터가 있는 값만을 특별한 형식으로 저장하여 메모리를 효율적으로 사용하려는 방법을 취함.
A sparse array is an array of data in which many elements have a value of zero. This is in contrast to a dense array, where most of the elements have non-zero values or are “full” of numbers. A sparse array may be treated differently than a dense array in digital data handling.
Member member = memberService.saveMember(snsType, token);
member.setSnsType("kakao"); /////<<<<<<<-- 여기서 실수하면 반영됨 상하로 트랜잭션이 있어서
memberService.emptyLogic();
=> dto 리턴 시 해결!
싱글톤 패턴
한 클래스마다 인스턴스를 하나만 생성하여 어디서든 참조
병렬로 여러 스레드에서 돌릴 경우 조심
직접 구현할 일 적음(스프링 빈)
장점
새로운 인스턴스를 계속해서 생성하지 않으므로 메모리 낭비가 적음
단점
코드량 많고, 테스트 어렵고, 자식 클래스 만들기 어렵, 클라이언트가 구체 클래스에 의존(dip 위반)
스프링이 모두 해결해 줌!
public class SingletonService {
private static final SingletonService instance = new SingletonService();
public static SingletonService getInstance() {
return instance;
}
// 생성자를 private으로 적용시켜 외부에서 인스턴스 생성을 막는다
private SingletonService() {
}
}
구조 패턴
클래스나 객체를 조합하여 더 큰 구조를 만들 수 있게 해줌
어댑터 패턴
타사의 라이브러리를 호출부의 변경 없이 사용하고 싶을 경우
Array.AsList(), Collections.enumeration(), Collections.list() 등도 어댑터 패턴을 활용한 예시
@Service
@Primary //얘랑 동일한 빈이 있어도 이게 우선이다라고 해줘야(override)
public class MailAdapter implements MailSenderA {
private final MailSolutionB mailSolutionB;
@Override
public void send(MailSolutionA.MailParam mailParam) {
MailSolutionB.MailParam param = MailSolutionB.MailParam.builder().mailTitle(mailParam.getTitle())
.mailBody(mailParam.getBody()).receiverEmail(mailParam.getEmail()).build();
mailSolutionB.sendApi(param);
}
}
위 처럼 A -> B로 바꿀 때, 기존 호출부를 그대로 두고
A를 쏘는 것 처럼 보이지만 사실 B로 쏘는 것
Interface를 써야 구현체를 교체할 수 있음 그래야 primary로 오버라이딩이 가능
모든 부분을 다 고치지 않고 한 곳만 고칠 때 좋음
브릿지 패턴
기능의 구현 클래스를 런타임 때 자유롭게 지정이 가능
주입을 set을 이용하여 동적으로 받음
기능과 구현을 분리하여 구현이 변경되더라도 기능 클래스 부분에 대한 변경은 필요 없음
if (StringUtils.isNotEmpty(member.getAccountNumber())) {
paymentService.setPaymentMethod(new CardMethodService()); // <<-- 서비스 주입을 런타임에
} else if (StringUtils.isNotEmpty(member.getPhoneNumber())) {
paymentService.setPaymentMethod(new PhoneMethodService());
}
paymentService.pay1(order.getAmount(), member);
컴포지트 패턴
일반적으로 직접 구현하지 않음
클라이언트 전체와 부분을 구별하지 않고 동일한 인터페이스로 사용
트리구조와 상당히 유사한 성격을 가지고 있다
하지만 기능이 너무 다른 클래스들에는 공통 인터페이스를 제공하기 어려움
데코레이터 패턴
일반적으로 직접 구현하지 않음
기존 코드를 수정하지 않고도 행동을 확장 가능
객체를 여러 데코레이터로 래핑하여 여러 행동들을 합성 가능
데코레이터를 너무 많이사용하면 코드가 필요 이상으로 복잡해짐
file, buffered reader 등등 New로 만들어서 래핑해서 합성하는 것
퍼사드 패턴
복잡한 로직들을 숨기고 간단한 인터페이스 함수만을 호출(추상화)
우리는 이미 MVC패턴을 통해서 퍼사드 패턴을 알게 모르게 사용하고 있다.
플라이웨이트 패턴
일반적으로 직접 구현하지 않음
인스턴스가 필요할 때마나 매번 생성하는게 아니라 가능한 공유해서 메모리를 절약
캐시, 레디스.. 디비 접근 최소화
String 객체를 생성하는 리터럴 방식의 String Constant Pool이 대표적인 플라이웨이트 패턴을 적용한 예시
@Test
void 스트링_플라이웨이트패턴_테스트() {
String str1 = "Hello";
String str2 = "Hello";
String str3 = new String("Hello");
// 서로 같다!
Assertions.assertSame(str1, str2);
// 서로 다르다!
Assertions.assertNotSame(str1, str3);
// 서로 같다!
Assertions.assertSame(str1, str3.intern());
/*
클래스가 JVM에 로드되면 모든 리터럴은 constant pool에 위치하게 된다.
그리고 리터럴을 통해 같은 문자를 생성한다면 풀 안의 같은 상수를 참조하게 되는데 이를 String interning이라고 한다.
String을 리터럴로 생성될 때 intern()이라는 메서드가 호출되고 이 intern() 메서드는
constant pool에 같은 문자가 존재하는지 확인 후 존재한다면 그 참조 값을 가지게 된다.
*/
}
@Test
void Integer_플라이웨이트패턴_테스트() {
Integer integer1 = Integer.valueOf("123");
Integer integer2 = Integer.valueOf("123");
// 서로 같다!
Assertions.assertSame(integer1, integer2);
Integer integer3 = Integer.valueOf("128");
Integer integer4 = Integer.valueOf("128");
// 서로 다르다!
Assertions.assertNotSame(integer3, integer4);
//직접 Integer.valueOf 함수 까보기!
//-128 ~ 127 까지는 캐싱이라 같고 그 이외 값은 캐싱안하고 새롭게 인스턴스 생성이라 다름
}
프록시 패턴
SRP와 연관
공통되는 로직 빼서 모듈화
AOP(aspect oriented programming; cglib etc..)
OOP로 독립적으로 분리하기 어려운 부가 기능을 모듈화 하는 방식
oop를 더 oop스럽게 해주는 방법
객체지향을 더 객체지향스럽게 만들어 주는 프로그래밍
transactional, cache, test에 사용하는..
spring 큰 특징 : DI, AOP(aspect), PSA(portable service abstraction)
import com.example.pattern.order.service.OrderService;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.objenesis.SpringObjenesis;
@Configuration
public class ProxyConfig {
private final SpringObjenesis objenesis = new SpringObjenesis();
@Bean
@Primary //order service말고 프락시를 빈으로 등록하도록 해야 aop 적용
public OrderService orderServiceProxy() {
return (OrderService) createCGLibProxy(OrderService.class, new MethodCallLogInterceptor());
}
private Object createCGLibProxy(Class<? extends Object> targetClass, MethodInterceptor interceptor) {
// Create the proxy
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(interceptor);
return enhancer.create();
}
}
행동 패턴
반복적으로 사용되는 객체들의 커뮤니케이션을 패턴화, 결합도를 최소화 하는 것이 목적
책임 연쇄 패턴
스프링 필터 사용(필터 체인)
검증 절차는 자유롭게 추가될 수 있어야 하고, 순서도 자유롭게 변경할 수 있어야 할 때
기존 클라이언트 코드를 수정하지 않고 앱에 새 핸들러를 도입 가능
@Configuration
public class FilterConfig {
// @Bean
// @Order(0)
public FilterRegistrationBean userAgentCheckFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(new UserAgentCheckFilter());
registrationBean.addUrlPatterns("/orders/*");
return registrationBean;
}
// @Bean
// @Order(1)
public FilterRegistrationBean memberCheckFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(new MemberCheckFilter());
registrationBean.addUrlPatterns("/orders/*");
return registrationBean;
}
}
커맨드 패턴
잘 사용하지 않음
기존 클라이언트 코드를 수정하지 않고 새로운 커맨드들을 도입 가능
요청부와 동작부를 분리시켜 주기 때문에 결합도를 낮출 수 있음
보통 비동기
@Test
void 혜택_발송_스레드_테스트() throws InterruptedException {
int numberOfThreads = 5;
CountDownLatch latch = new CountDownLatch(numberOfThreads);
//ExecutorService 객체가 Invoker를 의미
ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
for (int i = 0; i < numberOfThreads; i++) {
//Runnable 구현 객체가 Command를 의미;
// runnable interface 특징? 구현할 함수가 1개, callable과 디르게 반환타입이 없음
//CouponApiService 객체가 Receiver를 의미
Runnable doThread = new CouponService(new CouponApiService());
//위 대신 lambda로 대체가능
executorService.execute(() -> {
doThread.run();
latch.countDown();
});
}
latch.await();
}
Runnable -> void
Callable -> return 있음
반복자 패턴
잘 사용하지 않음
혜택이 아이템을 어떻게 구현하였는지 호출하는 쪽에서는 알 필요가 없음
즉 캡슐화가 잘 되어 있음
내부 구현을 외부로 노출시키지 않으면서도 모든 항목에 접근 가능
적용 시 덜 효율적이거나 과도하지는 않은지 확인
옵저버 패턴
한 객체의 상태가 변경되어 다른 객체들을 변경해야 할 필요성이 생겼을 때 사용(Pub/Sub 패턴이라고도 함)
message push, pull 하는 방식을 통해 결합도를 낮출 수/없앨 수 있음
rabbitMQ, kafka 등
느슨한 결합으로 객체간의 의존성 제거
너무 많이 사용하게 되면 상태 관리가 힘듦
private final ApplicationEventPublisher eventPublisher;
//함수 내부에서 호출 publish
eventPublisher.publishEvent(new OrderEvent(order.getId(), OrderType.CREATE));
//
import com.example.pattern.order.model.OrderEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@Async
public class OrderListener {
@EventListener
public void onApplicationEvent(OrderEvent event) {
log.info("주문이벤트 Published [orderId : {}, orderType : {}]", event.getOrderId(), event.getOrderType().toString());
log.info("이후 리뷰 알림 발송이 시작됩니다.");
log.info("이후 정산 API 호출이 시작됩니다.");
}
}
중재자 패턴
옵저버 패턴
1개의 publish에 대해 N개의 subcriber가 존재하고, observer가 pulling 이나 push방식을 통해 관리
중재자 패턴
M개의 publisher와 N개의 subcriber 사이에서 1개의 mediator를 통해 통신
메멘토 패턴
상태정보 저장 관련 메모리에 들고 있을 일 없음; 잘 사용하지 않음
캡슐화를 위반하지 않고 객체의 state 스냅샷을 생성할 수 있음
메멘토를 너무 자주 생성하면 많은 Ram을 소모할 수 있음
상태 패턴
잘 사용하지 않음
전략 패턴
하나의 메시지와 책임을 정의하고 이를 수행할 수 있는 다양한 전략을 만든 후, 다형성을 통해 전략을 선택해 구현을 실행
유사한 패턴들
팩토리, 추상 팩토리, 브릿지, 커맨드 패턴 등 이미 사용 중
특징
OCP 준수, 하지만 과도하게 복잡해질 수 있음
템플릿 메서드 패턴
잘 사용하지 않음, 요즘은 implement
interface는 내부 instance를 둘 수 없음 -> 필요 시 abstract class 를 사용
상속을 통해 슈퍼 클래스의 기능을 확장할 때 사용하는 대표적인 방법
변하지 않는 기능은 슈퍼 클래스에 만들어 두고, 확장할 기능은 서브 클래스에서 만듦
단점
알고리즘 변경 시 거의 모든 클래스에 수정이 가해질 수 있음
상속의 단점.. 결합이 커서
public abstract class Review { //abstract 사용예시
public void review() {
//부모 클래스에서 알고리즘의 골격을 정의
login();
selectBooks();
putContent();
selectEvaluation();
}
public void login() {
log.info("로그인 성공!");
}
public void selectBooks() {
log.info("리뷰 대상 상품 선택");
}
public void selectEvaluation() {
log.info("별점 선택");
}
public abstract void putContent(); //
}
방문자 패턴
잘 사용하지 않음
맴버는 맴버 클래스로, 리워드는 리워드 클래스로.. 둘을 섞지 않는다
OCP : 다른 클래스를 변경하지 않으면서 새로운 행동 도입 가능
SRP : 같은 행동의 여러 버전을 같은 클래스로 이동할 수 있음
public class PointBenefit implements Benefit {
@Override
public void getBenefit(GoldMember member) {
log.info("골드 멤버를 위한 포인트 제공 혜택");
}
@Override
public void getBenefit(VIPMember member) {
log.info("VIP 멤버를 위한 포인트 제공 혜택");
}
@Override
public void getBenefit(SilverMember member) {
log.info("실버 멤버를 위한 포인트 제공 혜택");
}
}
public class GoldMember implements Member {
// public void point() {
// log.info("골드 멤버를 위한 포인트 제공 혜택");
// }
// public void discount() {
// log.info("골드 멤버를 위한 할인 혜택");
// }
@Override
public void getBenefit(Benefit benefit) {
benefit.getBenefit(this);
}
}