본문 바로가기
Design Pattern

템플릿 메소드 패턴

by 권성호 2022. 4. 18.

사실이 아니라 공부한 내용과 생각을 정리한 글입니다. 언제든 가르침을 주신다면 감사하겠습니다.

 

해당 글은 김영한 님의 인프런 강의 스프링 핵심 원리 - 고급 편 을 학습하며 정리한 내용입니다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8/dashboard

 

스프링 핵심 원리 - 고급편 - 인프런 | 강의

스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., - 강의 소개 | 인프런...

www.inflearn.com

 

1.  해결하고자 하는 문제 

템플릿 메서드 패턴이 해결하고자 하는 문제는 변하지 않는 것과 변하는 것을 분리해 모듈화 하는 것입니다.

객체지향 관점에서 단일 책임 원칙을 달성하기 위한 패턴이라고 생각됩니다.

 

2.  문제를 푸는 방식

상속을 통해 문제를 해결합니다.

템플릿 메소드 패턴 다이어그램

변하지 않는 부분을 추상 클래스에 템플릿 형태로 두고, 이를 상속하여 변하는 부분만 제 정의하는 방식으로 변하는 부분과 변하지 않는 부분을 분리하고 모듈화 할 수 있습니다.

 

3.  예제 코드를 통한 이해

아래와 같은 비즈니스 로직이 있다고 가정합니다.

public void logic1() {
    //비즈니스 로직 실행
    log.info("비즈니스 로직1 실행");
    //비즈니스 로직 종료
}

logic1 메서드의 실행 시간을 로깅해야 한다면 가장 단순한 형태로 아래와 같이 구현할 수 있습니다.

public void logic1() {
    long startTime = System.currentTimeMillis();
    //비즈니스 로직 실행
    log.info("비즈니스 로직1 실행");
    //비즈니스 로직 종료
    long endTime = System.currentTimeMillis();
    long resultTime = endTime - startTime;
    log.info("resultTime={}", resultTime);
}

이를 통해 실행 시간 로깅이라는 요구사항은 만족했지만, 위 코드는 핵심 비즈니스 로직과 로깅을 위한 부가 로직이 혼재되어 있습니다.

만약, logic1을 포함해, 특정 패키지의 모든 public method에 대해 실행시간 로깅을 추가해 달라는 요청이 들어온다면 어떻게 해야 할까요?

위 방식대로 한다면 기존에 존재하던 모든 public method에 하나하나 로깅을 위한 로직을 추가해야 합니다.

그런데, 로깅을 위한 로직은 변할까요?? 변하지 않습니다. 

모든 public method는 핵심 비즈니스 로직만 변할 뿐 로깅을 위한 로직은 동일합니다.

 

이를 템플릿 메서드 패턴을 적용해 개선해 보겠습니다.

우선 변하지 않는 로깅 로직을 담아 둘 추상 클래스를 정의합니다.

@Slf4j
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();
}

execute method는 로깅을 위한 템플릿 역할을 하고 변하는 부분은 추상 클래스를 상속받는 쪽으로 구현을 위임했습니다.

익명 클래스를 통해 앞서 살펴본 logic1 method와 동일한 기능을 하는 메서드를 만들면 아래와 같습니다.

public void logic2() {
    AbstractTemplate template1 = new AbstractTemplate() {
        @Override
        protected void call() {
            log.info("비즈니스 로직1 실행");
        }
    };
    template1.execute();
}

 

이제 로깅을 위한 코드(변하지 않는 부분)와 핵심 비즈니스 로직(변하는 부분)이 분리되었고 모듈화 되었습니다.

AbstractTemplate을 상속받은 후 call method를 override 해서 그 속에 로깅을 원하는 로직을 넣으면 됩니다.

 

이 방식을 통해 최초에 생각했던 변하는 부분과 변하지 않는 부분을 분리하고 모듈화 하는 것에는 성공했으나 또 다른 문제가 발생했습니다.

 

4.  문제점

 

핵심 비즈니스 로직을 구현하는 클래스는 반드시 추상 템플릿 클래스를 상속해야 합니다.

상속을 사용하면 컴파일 타임에 의존성이 결정되기 때문에 상당히 강한 의존관계를 갖게 됩니다. 

또한, 자식 클래스는 부모 클래스의 모든 구현을 알게 되기 때문에 캡슐화의 관점에서도 설계가 망가집니다.

 

즉, 부모 클래스에 변화로 인해 이를 상속하고 있는 모든 자식 클래스에게 영향도가 퍼질 수 있고 특정 자식 클래스로 인해 부모 클래스의 구현이 변할 수도 있는 구조입니다.

 

변경에 상당히 열려 있기 때문에 개방 패쇄 원칙을 위배합니다.

자식 클래스에서 부모 클래스의 구현을 바꿀 여지가 있기 때문에 리스 코프 치환 원칙을 위배합니다.

 

이러한 문제를 개선하면서, 템플릿 메서드 패턴의 장점을 그대로 가져간 페턴이 바로 전략 패턴입니다.

다음 글에서는 전략 패턴(https://ksh-dev.tistory.com/62)에 대해 알아보겠습니다.

 

 

 

 

 

 

'Design Pattern' 카테고리의 다른 글

프록시 패턴 & 데코레이터 패턴  (1) 2022.04.18
전략 패턴  (0) 2022.04.18

댓글