템플릿 메서드 패턴
부모클래스에 알고리즘의 골격인 template을 정의하고 일부 변경되는 로직은 자식클래스에서 정의하는것이다.
로그 추적기를 사용하는 구조는 모두 동일하다. 이런 boilerplate 를 template으로 만들어서 해결하자
public abstract class AbstractTemplate {
public void execute() {
long startTime = System.currentTimeMillis();
//비즈니스 로직 실행
call(); //상속
//비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
protected abstract void call();
}
public class SubClassLogic1 extends AbstractTemplate {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
}
template의 call 메소드를 오버라이딩 하고 아래 첫번째 테스트 로직처럼 execute를 실행하면 boilerplate가 알아서 감싸지면서 실제 원하는 비지니스 로직도 호출하게 된다.
자식이 부모를 상속해서 overriding 한 메소드는 자식 것을 호출하게 된다.
@Test
void templateMethodV1() {
AbstractTemplate template1 = new SubClassLogic1();
template1.execute();
}
@Test
void templateMethodV2() {
AbstractTemplate template1 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
};
log.info("클래스 이름1={}", template1.getClass());
template1.execute();
}
굳이 직접 추상클래스를 상속할 필요없이 익명 클래스를 만들어서 사용할 수도 있다.
public abstract class AbstractTemplate<T> {
private final LogTrace trace;
public AbstractTemplate(LogTrace trace) {
this.trace = trace;
}
public T execute(String message) {
TraceStatus status = null;
try {
status = trace.begin(message);
//로직 호출
T result = call();
trace.end(status);
return result;
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
protected abstract T call();
}
Controller에 적용할 template class이다.
public class OrderControllerV4 {
private final OrderServiceV4 orderService;
private final LogTrace trace;
@GetMapping("/v4/request")
public String request(String itemId) {
AbstractTemplate<String> template = new AbstractTemplate<>(trace) {
@Override
protected String call() {
orderService.orderItem(itemId);
return "ok";
}
};
return template.execute("OrderController.request()");
}
}
스프링이 주입해준 LogTrace를 template에게 넘겨서 template에게 도 주입해준다.
제네릭 타입은 String으로 결정이되고 call도 String을 return하게 된다.
하지만 부모 클래스의 기능을 전혀 사용하지 않지만 부모 클래스를 알아야 한다. == 부모 클래스가 변경되거나 새롭게 추가되는 abstract method가 생기면 이것을 상속하는 모든 자식들이 그것을 구현해야한다.
잘못된 의존관계를 , 상속의 단점을 제거 할 수 있는 디자인패턴이 전략패턴이다.
전략 패턴
Context에서 바로 상속하는 것이아니라
Context는 부모클래스처럼 변하지 않는 template 역할을 하고 Stategy 가 변하는 로직을 담당하게 된다.
public class ContextV1 {
private Strategy strategy;
public ContextV1(Strategy strategy) {
this.strategy = strategy;
}
public void execute() {
long startTime = System.currentTimeMillis();
//비즈니스 로직 실행
strategy.call(); //위임
//비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
}
public class StrategyLogic1 implements Strategy {
@Override
public void call() {
log.info("비즈니스 로직1 실행");
}
}
void strategyV1() {
StrategyLogic1 strategyLogic1 = new StrategyLogic1();
ContextV1 context1 = new ContextV1(strategyLogic1);
context1.execute();
}
template에 내가 변경하고 싶은 로직을 주입해주는 방식으로 호출하게 된다. spring DI처럼 동작한다.
이제 template에 코드가 변경이 되어도 실제 strategy 로직은 영향을 받지 않는다.
탬플릿메서드 패턴에서는 상속관계라 직접적인 영향을 끼쳤지만 이제는 아니다.
@Test
void strategyV4() {
ContextV1 context1 = new ContextV1(() -> log.info("비즈니스 로직1 실행"));
context1.execute();
ContextV1 context2 = new ContextV1(() -> log.info("비즈니스 로직2 실행"));
context2.execute();
}
interface를 사용하고 하나의 메소드만 존재하기에 functinoal interface 처럼 람다를 사용할 수 있다.
Context 와 Strategy를 한번 조합하고 나면 전략을 변경하기 어렵다. 싱글톤인데 setter같은 것을 열어두면 동시성 문제가 생길 수도 있다.
그러면 싱글톤으로 하지말고 Context2를 새로 생성해서 원하는 전략을 조립하고 사용 하는것이 좋다.
하지만 실시간으로 전략을 야무지게 변경하고 싶다면 어떻게 해야하나
전략을 이제 필드에 담지 말고 인수로 전달하자
public class ContextV2 {
public void execute(Strategy strategy) {
long startTime = System.currentTimeMillis();
//비즈니스 로직 실행
strategy.call(); //위임
//비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
}
@Test
void strategyV3() {
ContextV2 context = new ContextV2();
context.execute(() -> log.info("비즈니스 로직1 실행"));
context.execute(() -> log.info("비즈니스 로직2 실행"));
}
유연하게 전략을 변경하여 실행이 가능하다.
우리가 하고자하는것은 선조립 , 후 실행이 아니라 변하는 부분과 변하지 않는 부분을 분리하는 것이다. 방금한 것이 우리가 원하는것과 가깝다.
템플릿 콜백 패턴
js 에서 함수는 일급객체라 함수를 인자로 넘겨 받을 수 있다. 이를 콜백함수라 한다.
자바 역시 람다로 콜백함수를 인자로 넘겨주는 것이 가능해 졌다.
스프링에서 JdbcTemplate , RestTemplate.. .이런 xxxTemplate 은 다 템플릿 콜백 패턴으로 이루어져있다.
Context -> template
Strategy -> callback
public interface Callback {
void call();
}
public class TimeLogTemplate {
public void execute(Callback callback) {
long startTime = System.currentTimeMillis();
//비즈니스 로직 실행
callback.call(); //위임
//비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime={}", resultTime);
}
}
void callbackV2() {
TimeLogTemplate template = new TimeLogTemplate();
template.execute(() -> log.info("비즈니스 로직1 실행"));
template.execute(() -> log.info("비즈니스 로직2 실행"));
}
전략패턴에서 전략을 람다(callback)으로 인자로 전달하는것과 동일하다.
'WEB > Spring' 카테고리의 다른 글
스프링 핵심 원리 - 고급편 4) 동적 프록시 기술 (0) | 2022.06.07 |
---|---|
스프링 핵심 원리 - 고급편 4) 프록시 패턴과 데코레이터 패턴 (0) | 2022.06.06 |
스프링 핵심원리 고급편 2) ThreadLocal (0) | 2022.06.05 |
스프링 핵심 원리 - 고급편 1) 예제만들기 (0) | 2022.01.02 |
[java brains , spring docs] Aspect Oriented Progamming (0) | 2021.10.13 |