템플릿 메서드 패턴, 전략패턴(스프링 핵심원리 고급편)

2023. 4. 25. 17:38개발

반응형

김영한님 강의(스프링 핵심원리 고급편)에서 디자인패턴을 여러가지를 다뤄주고있어서 보고있다.

 

템플릿 메서드 패턴

변하지 않는 부분을 부모클래스에 명시해두고 자식클래스에서는 부모 클래스를 상속받아서 변경이 있는 핵심로직만 구현하여 사용하는 형태.

여기서의 문제는 변하지 않는 로직(단순 로깅 실질적인 업무에 관련X)과 핵심로직(비즈니스로직) 2가지가 있다고 할 때 전체적인 틀을 지정해두고 비즈니스로직은 상속받은 서브클래스 들이 구현함으로써 해결하는 형태인듯 하다.

예를들면 아래와 같음.

AbstractTemplate라는 추상클래스를 명시해두고 상속받은 서브 클래스는 모두 call에 특정 비즈니스 로직을 작성하여 수행한다.

@Slf4j
@RequiredArgsConstructor
public abstract class AbstractTemplate<T> {

    private final LogTrace 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();

}

사용할때는 해당 클래스를 생성하면서 익명 내부 클래스를 생성하여야 한다..!

@GetMapping("/v4/request")
    public String request(String itemId) {

        AbstractTemplate<String> abstractTemplate = new AbstractTemplate<>(trace) {
            @Override
            protected String call() {
                orderService.orderItem(itemId);
                return "ok";
            }
        };

		String execute = abstractTemplate.execute("OrderController.request");
        return execute;

    }

그런게 아니라면 별도로 상속받아서 구현한 클래스들을 만들어야한다.

@Slf4j
public class SubLogic1 extends AbstractTemplate{
    @Override
    protected void call() {
        log.info("비즈니스 로직1 수행");
    }
}

테스트코드에서 사용할 때 모습

@Test
    void templateMethodV1() {
        AbstractTemplate subLogic1 = new SubLogic1();
        subLogic1.execute();

    }

템플릿메서드 패턴을 사용함에 있어서 생기는 여러가지 고민은 다음과 같다고 한다.

자식클래스에서 상속받아서 사용하게 되는데 실질적으로 특정 메서드만 구현하여 필요한 부분을 만드는건 편리하지만.. 자식에서 몰라도 될 여러가지 메서드들을 상속받음으로써 의존관계가 강하게 생긴다!

만약 부모클래스에서 구현해야 하는 클래스가 여러개가 생기는 경우 추가적으로 상속받은 자식클래스에서는 추가적으로 구현처리 해야한다.(예를들어 부모 클래스에 call메서드 말고 nextCall() 메서드가 추가됨 )

그리고 자식클래스들이 계속하여 늘어나는걸 방지하기위해 익명클래스를 사용하는 경우 아무래도 코드가 조금 지저분해 지는 경향도 있다..!

탬플릿 메서드 패턴과 비슷한 역할을 하면서 상속의 단점을 제거 할 수 있는 디자인 패턴인 전략패턴이 존재한다!!!

 

전략패턴이란..?

전략패턴은 탬플릿 메서드 패턴과 비슷하다.

그러나 변하지 않는 부분을 별도의 Context라는 곳에 둔다. 변하는 부분은 Strategy라는 인터페이스를 만들고 해당 인터페이스를 구현하도록 한다.

템플릿 패턴에서 변하지 않는 로직부분이 Context가 되고, Strategy부분이 별도로 구현받아서 상속하던 핵심로직 부분이 된다.

상속이 아닌 위임으로 해결하는 형태이다!!!

Strategy 인터페이스 모습

public interface Strategy {

    void call();
}

Context 클래스 모습

@Slf4j
public class Context {

    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 strategyV2() {

        ContextV2 contextV2 = new ContextV2();
        Strategy strategy = new StrategyLogic1();
        contextV2.execute(strategy);
        contextV2.execute(() -> log.info("비즈니스 로직2 수행"));
        
    }

해당 Strategy라는인터페이스를 구현한 StrategyLogic1라는 클래스를 명시해두고 사용해도 된다. 그게 아니라면 메서드를 하나만 두어서 익명 람다 클래스로 편리하게 사용 할 수도 있다..!

@Slf4j
public class StrategyLogic1 implements Strategy{
    @Override
    public void call() {
      log.info("비즈니스 로직1 수행");
    }
}

결국은 어떠한 방식으로 편리하게 사용할지에 대한 고민들이 녹아들어 있는것 들이 디자인 패턴이라고 볼 수 있을듯 하다.. 팩토리 패턴, 템플릿패턴, 전략패턴도 모두 실무에서 사용되고있고 어떤방식으로 편하게 사용하고싶은지에 대한 방향이 조금씩 다른것 같다..!

반응형