템플릿 메서드패턴

ForLearn·2022년 9월 22일
0

스프링 핵심원리 

목록 보기
1/1

템플릿 메서드 패턴

변하지 않는 것과 변하는 것의 분리

비슷한 패턴의 코드가 반복되는 경우가 있다. 보통 비슷한 패턴의 변하지 않는 코드는 대부분 부가적인 기능인 경우가 많고, 변할 가능성이 있는 코드는 핵심 기능인 경우가 많다.
이 경우 핵심 기능과 부가 기능을 분리하는 것이 좋은 설계라고 볼 수 있다.

이와 비슷한 문제는 템플릿 메서드 패턴으로 해결이 가능하다.

예제 - 변하는 부분과 변하지 않는 부분의 혼재

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

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

    @Test
    void templateMethodV0(){
        logic1();
        logic2();
    }

위의 코드를 보면 비지니스 로직(핵심 기능 & 변하는 부분 ) 코드와 부가기능 코드가 함께 있는 것을 볼 수 있다.

loginc1() 과 logic2() 의 부가 기능은 같고 핵심기능은 다른데 이를 분리해 보자.

템플릿 메서드 패턴을 활용한 문제 해결

템플릿 메서드 패턴은 템플릿이라는 틀에 변하지 않는 부분을 몰아둔 후 일부 변하는 부분을 별도로 호출하여 핵심 기능과 부가 기능을 분리한다.

@Slf4j
public abstract class AbstractTemplate {

    public void execute(){
        long startTime = System.currentTimeMillis();
        // 비즈니스 로직 실행
      //  log.info("비즈니스 로직1 실행 ");
        call();  // 상속으로 해결한다. 자식 클래스에 따라 달라짐.
        // 비즈니스 로직 종료
        long endTime = System.currentTimeMillis();
        long resultTime = endTime - startTime;
        log.info("resultTime={}", resultTime);
    }

    protected abstract void call();
}

위의 AbstractTemplate 를 보면 execute() 메서드가 하나의 템플릿이 된 것을 볼 수 있고, call() 을 이용하여 변하는 부분을 처리한다.

⭐⭐템플릿 메서드 패턴은 부모 클래스에 변하지 않은 템플릿 코드를 둔다. 그리고 변하는 부분은 자식 클래스에 두고 상속과 오버라이딩을 사용하여 처리한다.
➡상속을 이용하여 핵심 기능과 부가 기능의 분리

@Slf4j
public class SubClassLogic1 extends AbstractTemplate{

    @Override
    protected void call() {
        log.info("비즈니스 로직1 실행");
    }
}

@Slf4j
public class SubClassLogic2 extends AbstractTemplate{

    @Override
    protected void call() {
        log.info("비즈니스 로직2 실행");
    }
}

AbstractTemplate 을 구현한 자식 클래스 핵심 기능을 오버라이딩 하여 핵심 기능과 부가 기능을 분리했다.

    @Test
    void templateMethodV1(){
        AbstractTemplate template1 = new SubClassLogic1();
        template1.execute();

        AbstractTemplate template2 = new SubClassLogic1();
        template2.execute();
    }
  • template1.execute() 를 호출하면 템플릿 로직인 AbstractTemplate.execute() 를 실행한다. 여기서 중간에 call() 메서드를 호출하는데, 이 부분이 오버라이딩 되어있다.
  • 따라서 현재 인스턴스인 SubClassLogic1 인스턴스의 SubClassLogic1.call() 메서드가 호출된다.

익명 내부 클래스

템플릿 메서드 패턴은 SubClassLogic1 , SubClassLogic2 처럼 클래스를 계속 만들어야 하는 단점이 있다.

이와 같은 단점은 익명 내부 클래스 통해 해결할 수 있다.

    @Test
    void templateMethodV2(){
        AbstractTemplate template1 = new AbstractTemplate(){
            @Override
            public void call() {
              log.info("비즈니스 로직1 실행");
            }
        };
        log.info("클래스 이름1 ={}",template1.getClass());
        template1.execute();

        AbstractTemplate template2 = new AbstractTemplate(){
            @Override
            public void call() {
                log.info("비즈니스 로직1 실행");
            }
        };
        log.info("클래스 이름2 ={}",template2.getClass());
        template2.execute();
    }
}

단일 책임 원칙 (SRP)

템플릿 메서드 패턴을 사용한 장점은 코드 몇줄을 줄이는 것이 아니다.

핵심 기능과 부가 기능을 분리하여 단일 책임 원칙을 지키 면서 변경지점을 하나로 모아 변경에 쉽게 대처할 수 있는 구조를 만든 것이 핵심이다.

템플릿 메서드 패턴의 단점

템플릿 메서드 패턴은 상속을 사용하여 문제를 해경한다. 때문에 상속의 단점을 그대로 안고 간다.

특히 자식 클래스가 부모 클래스와 컴파일 시점에 강하게 결합되는 문제가 있다.
부모 클래스의 기능은 사용하지 않는데, 부모 클래스의 변화에 자식 클래스가 영향을 받는 문제가 있다.

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

REFERENCE

스프링 핵심원리 고급편 - 김영한
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
스프링 입문을 위한 자바 객체지향의 원리와 이해

0개의 댓글