IT, 코딩/디자인 패턴

[디자인 패턴] 템플릿 메서드 (Template Method) 패턴이란?

우크박스_ 2024. 4. 8. 17:44

템플릿 메서드 (Template Method) 패턴이란?

"작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다. 템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있습니다." [GOF]

템플릿 메서드 (Template Method) 패턴은 전체적으로 동일하면서 부분적으로 다른 구문으로 구성된 메서드의 중복을 최소화할 때 유용한 패턴이다. 동일한 기능을 상위 클래스에서 정의하면서 확장/변화가 필요한 부분만 서브 클래스에서 구현할 수 있도록 한다.

즉, 부모 클래스에 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는 것이다. 이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고, 특정 부분만 재정의할 수 있다.

결국 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.

구성

AbstractClass : 템플릿 메서드를 정의하는 클래스

  • 상위 클래스에서 공통 알고리즘을 정의하고, 하위 클래스에서 구현될 기능을 primitive 또는 hook 메서드로 정의하는 클래스이다.

ConcreteClass : 물려받은 primitive 또는 hook 메서드를 구현하는 클래스

  • 상위 클래스에 구현된 템플릿 메서드의 일반적인 알고리즘에서 하위 클래스에 적합하게 primitive 또는 hook 메서드를 오버라이드하는 클래스이다.

hook 메서드는 추상 클래스에 들어있는, 아무 일도 하지 않거나 기본 행동을 정의하는 메서드로 서브 클래스에서 오버라이드 할 수 있다.

 

예제 알아보기

예제 코드를 보기 전에 템플릿 메서드 (Template Method) 패턴은 변하는 것과 변하지 않는 것을 분리하는 디자인 패턴이라는 것을 기억하자.

예제 코드는 김영한 님의 스프링 핵심 원리 - 고급편 을 참고하였습니다.

    @Test
    // 실행
    void templateMethodV0() {
        logic1();
        logic2();
    }

    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);
    }

위의 코드에서 비즈니스 로직에 해당하는 부분은 변경로직 으로 오직 한 줄이며 나머지는 모두 중복되는 코드이다.

그렇다면 과연 중복되는 코드의 문제점은 무엇일까?

가독성도 문제지만 가장 중요한 것은 변경확장에 용이하지 않다는 점이다. logic 메서드가 logic1() ~ logic100() 까지 존재한다고 생각해보자. 중복 코드에 변경이 생기는 경우 100개의 메서드를 일일이 찾아서 코드를 모두 수정해줘야할 것이다. 이는 매우 귀찮은 일이기도 하며 최악의 경우에는 개발자의 실수로 어느 한 메서드만 잘못 변경할 수도 있다.

템플릿 메서드 (Template Method) 패턴은 변하는 부분과 변하지 않는 부분을 분리하여 위와 같은 문제를 해결한다.

 

AbstractClass : 공통 알고리즘 정의

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

 

ConcreteClass: 변경 로직 정의

//비즈니스 로직1 처리
public class SubClassLogic1 extends AbstractTemplate{
    @Override
    protected void call() {
        log.info("비즈니스 로직1 실행");
    }
}
//비즈니스 로직2 처리
public class SubClassLogic2 extends AbstractTemplate{
    @Override
    protected void call() {
        log.info("비즈니스 로직2 실행");
    }
}

 

실행

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

    AbstractTemplate template2 = new SubClassLogic2();
    template2.execute();
}

이제 비즈니스 로직이 100까지 확장된다고 하더라도 공통 알고리즘은 전혀 신경 쓸 필요가 없다. 오직 비즈니스 로직을 처리하는 부분에만 신경 쓸 수 있게 된 것이다.

하지만 비즈니스 로직이 추가될 때마다 클래스를 계속 만들어야 한다는 단점이 있다.

익명 내부 클래스를 사용하면 이런 단점을 보완할 수 있다.

@Test
void templateMethodV2() {
    //익명 클래스 1 선언
    AbstractTemplate template1 = new AbstractTemplate() {
        @Override
        protected void call() {
            log.info("비즈니스 로직1 실행");
        }
    };
    template1.execute();

    //익명 클래스 2 선언
    AbstractTemplate template2 = new AbstractTemplate() {
        @Override
        protected void call() {
            log.info("비즈니스 로직2 실행");
        }
    };
    template2.execute();
}

 

단점

템플릿 메서드(Template Method) 패턴에도 단점이 있다.

상속으로 인한 문제

템플릿 메서드 패턴은 상속과 오버라이딩을 통한 다형성으로 문제를 해결한다. 따라서 상속에서 오는 단점들을 그대로 안고간다.

상속은 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다.

위의 예제에서 자식 클래스인 SubClassLogic1, SubClassLogic2는 각각의 비즈니스 로직만을 정의하였다.

자식 클래스는 부모 클래스인 AbstractTemplate의 기능을 전혀 사용하지 않았음에도 불구하고 상속 관계로 인해 부모 클래스를 강하게 의존하게 된다. 이러한 잘못된 의존관계 때문에 부모 클래스를 수정하면, 자식 클래스에도 영향을 줄 수 있다.

반응형