스프링 핵심 원리 - 고급편 - 섹션3. 템플릿 메서드 패턴과 콜백 패턴
CS/김영한 스프링 강의

스프링 핵심 원리 - 고급편 - 섹션3. 템플릿 메서드 패턴과 콜백 패턴

이 섹션에서 템플릿 메서드 패턴, 전략 패턴, 탬플릿 콜백 패턴을 알아볼거다.

 

 

전 섹션에서 본 로그추적기를 도입하려고 했는데, 막상 해놓고 보니 부가적으로 반복되는 코드가 너무 장황해졌다.

핵심기능은 단 한줄인데 단지 로그 추적을 위한 부가기능으로 뭔가 앞뒤로 덕지덕지 붙어서 너무 보기 흉하다. 이건 메소드가 2개라서 그나마 괜찮아 보이지 막 클래스와 메소드가 100개씩 넘어가면 더 힘들어진다. 하지만 반복되는 부분을 메서드로 따로 빼려고 해도 핵심 기능이 try catch로 감싸져 있어 이것도 쉽지 않다.

 

그래서 이 문제를 해결하는 디자인 패턴이 템플릿 메서드 패턴이다.

일단 이게 뭔지 알기 위해 테스트코드를 작성해본다.

 

주변은 같은데 안에만 다를 때 추상화 클래스 상속을 사용하면 된다.

 

이게 뭔지는 계속 보면 이해된다. 상위 클래스꺼 실행하다가 내가 override한 함수를 실행하는 것.

 

이걸 실제로 적용해보자.

 

 

 

비교해보면 알겠지만 앞에서 했던 trace.begin; stats = ... , trace.end;... 하던 부분들을 추상화에 넣고 진행한다.

물론 그래도 내부 클래스 때문에 지저분해 보이지만 반복 코드를 줄였다. 하지만 목적은 변하는 코드와 변하지 않는 코드 분리였기에 목적은 달성했다.

 

 

 

이렇게 한게 예전에 배웠던 5가지 원칙 SOLID 중 단일 책임 원칙을 지킨 것. 좋은 설계란 모듈화와 분리를 잘해놔서 한번 고칠 때 단순하게 그 부분만 고치면 되는 것. 만들 때는 잘 모르지만 수정하게 될 때 이 진가가 드러난다.

 

 

이 디자인 패턴은 gof의 디자인 패턴에 나와있는 건데, 변하지 않는 코드는 부모한테 주고, 재정의하는 코드만 자식한테 줘서 다형성 문제를 해결하는 것.

하지만 큰 단점이 있는데, 추상 클래스이다 보니 자식이 부모한테 너무 의존하는 것. 무슨 말이냐면 각각의 클래스가 독립적이어야 하는데 부모가 함수가 새로 생기거나 하면 자바 클래스 구조로 사용하지도 않을거지만 다 정의해야 하며, 이는 잘못된 설계다.

 

그래서 이 템플릿 메서드 패턴과 비슷한 역할을 하면서 상속의 단점을 제거할 수 있는 디자인 패턴이 바로 전략 패턴이다.

 

 

아까같이 필요한 메소드가 들어있는 추상메서드 통채로 상속받는게 아닌 필요한 함수 부분만 interface로 상속하는 것.

여기서 외부 함수를 가져와서 쓰는 거라 독립적인게 가능했던것.

 

 

이것도 내부클래스 및 안의 메소드가 call 하나라 람다도 사용 가능하다.

 

 

 

context를 만들 때 주입해서 만들고 사용하는 것도 있지만, 함수를 실행할 때 주입하는 방법도 있다.

@Slf4j
public class ContextV2Test {

    /**
     * 전략 패턴 사용
     */
    @Test
    void strategyV1() {
        ContextV2 context = new ContextV2();
        context.execute(new StrategyLogic1());
        context.execute(new StrategyLogic2());
    }

    /**
     * 전략 패턴 익명 내부 클래스
     */
    @Test
    void strategyV2() {
        ContextV2 context = new ContextV2();
        context.execute(new Strategy() {
            @Override
            public void call() {
                log.info("비즈니스 로직1 실행");
            }
        });
        context.execute(new Strategy() {
            @Override
            public void call() {
                log.info("비즈니스 로직2 실행");
            }
        });
    }

    /**
     * 전략 패턴 람다
     */
    @Test
    void strategyV3() {
        ContextV2 context = new ContextV2();
        context.execute(() -> log.info("비즈니스 로직1 실행"));
        context.execute(() -> log.info("비즈니스 로직2 실행"));
    }


}

 

이것도 함수를 실행할 때마다 매번 주입해줘야 하는거라 각각 장단점이 있다.

 

 

사실 바로 위에서 했던 게 콜백패턴인데, 실행 가능한 코드를 넘겨주고 나중에 넘겨받은 함수 안에서 실행하기 때문에 콜백이라고 부름.

 

xxxTemplate가 붙는다면 콜백 패턴으로 만들어져 있다 생각하면 된다.

 

 

 

이걸 실제로 적용해보자.

 

 

 

 

 

조금 더 깔끔해졌다.

 

 

이제 코드로 할 수 있는건 다 했다.

하지만 여기까지 해서도 해결 못한 점은, 이 추적 기능을 추가하고 싶다면 결국 안의 원본 코드를 수정해야 하는 것이다. 즉, template.execute... 를 직접 함수 안에서 사용해야 한다는 것.

 

이걸 해결 하는 방법은 다음 섹션에서 보자.