본문 바로가기

ComputerScience/DesignPattern

[DesignPattern] 템플릿 메소드 패턴 (Template Method Pattern)

템플릿 메소드 패턴

템플릿 메소드 패턴은 알고리즘의 골격을 정의합니다.

템플릿 메소드를 사용하면 알고리즘의 일부 단계를 서브 클래스에서 구현할 수 있으며, 알고리즘의 구조는 그대로 유지하면서 알고리즘의 특정 단계를 서브 클래스에서 재정의할 수도 있습니다.

 

쉽게 말해 템플릿 메소드 패턴은 알고리즘의 템플릿을 만들고,
이 템플릿은 일련의 단계로 알고리즘을 정의한 메소드입니다.

 

이미지 출처: https://en.wikipedia.org/wiki/Template_method_pattern

위 UML Class Diagram 에서, AbstractClass는 templateMethod() 작업을 정의합니다. 이 메서드는 행동의 뼈대를 구현하고, primitive1()과 primitive2() 메시지를 자기 자신에게 보냅니다.

이 메시지들은 SubClass1에서 구현되어 있기 때문에 이 서브클래스에서 해당 알고리즘의 일부를 변형할 수 있게 합니다.

 

예를 봅시다.

 

AbstractClass: CaffeineBeverage

public abstract class CaffeineBeverage {
    // 템플릿 메소드
    // 서브 클래스에서 오버라이드를 막기 위해 final로 선언
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    // 서브 클래스에서 구현해야하는 알고리즘의 단계는 abstract 메소드로 구현
    abstract void brew();

    abstract void addCondiments();

    void boilWater() {
        System.out.println("물 끓이는 중");
    }

    void pourInCup() {
        System.out.println("컵에 따르는 중");
    }
}

 

ConcreteClass: Tea

public class Tea  extends CaffeineBeverage{
    @Override
    void brew() {
        System.out.println("찻잎을 우려내는 중");
    }

    @Override
    void addCondiments() {
        System.out.println("레몬을 추가하는 중");
    }
}

 

클라이언트 입장에서의 동작은 다음과 같습니다.

  1. tea.prepareRecipe()
  2. 추상 클래스에 구현된 템플릿 prepareRecipe() 메소드 호출
  3. 추상 클래스에서의 추상 메소드들은 서브 클래스의 구현에 따름

템플릿 메소드 속 후크

후크(hook)는 추상클래스에서 선언되지만 기본적인 내용만 구현되어있거나 아무 코드도 들어있지 않은 메소드입니다.

서브클래스는 후크를 이용하여 다양한 위치에서 알고리즘에 끼어들 수도 있고, 그냥 무시하고 넘어갈 수도 있습니다.

AbstractClass with hook: CaffeineBeverageWithHook

public abstract class CaffeineBeverageWithHook {
    // 템플릿 메소드
    // 서브 클래스에서 오버라이드를 막기 위해 final로 선언
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }

    // 서브 클래스에서 구현해야하는 알고리즘의 단계는 abstract 메소드로 구현
    abstract void brew();

    abstract void addCondiments();

    void boilWater() {
        System.out.println("물 끓이는 중");
    }

    void pourInCup() {
        System.out.println("컵에 따르는 중");
    }

    // true를 반환하는 아무 작업이 없는 hook 메소드
    boolean customerWantsCondiments() {
        return true;
    }
}

 

 

ConcreteClass with hook: TeaWithHook

public class TeaWithHook extends CaffeineBeverageWithHook{
    @Override
    void brew() {
        System.out.println("찻잎을 우려내는 중");
    }

    @Override
    void addCondiments() {
        System.out.println("레몬을 추가하는 중");
    }

    @Override
    boolean customerWantsCondiments() {
        String answer = getUserInput();

        return answer.toLowerCase().startsWith("y");
    }

    private String getUserInput() {
        String answer = null;

        System.out.println("차에 레몬을 넣을까요? (y/n)");
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        try {
            answer = in.readLine();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        if(answer == null) {
            return "no";
        }
        return answer;
    }
}

 

구현을 해도되고 안 해도 되면, 추상 메소드랑 뭐가 다르지?

추상 메소드 (Abstract Method)

  • 추상 메소드는 템플릿 메소드에서 선언되지만 구현은 추상 클래스에서 제공되지 않습니다. 대신, 해당 추상 메소드의 구체적인 구현은 서브클래스에서 제공되어야 합니다.
  • 템플릿 메소드의 일부로 존재하며, 서브클래스에게 특정 단계의 구현을 강제하는 역할을 합니다.
  • 추상 메소드는 서브클래스에서 반드시 구현되어야 하는 메소드입니다.

후크 메소드 (Hook Method)

  • 후크 메소드는 템플릿 메소드에서 선언되고 기본 구현이 제공됩니다. 서브클래스는 필요에 따라 후크 메소드를 오버라이드하여 원하는 동작을 구현할 수 있습니다.
  • 템플릿 메소드의 일부로 존재하지만, 서브클래스에 의해 선택적으로 오버라이드될 수 있는 메소드입니다.
  • 후크 메소드는 서브클래스에서 기본 동작을 수정하거나 확장할 수 있는 지점을 제공합니다.

서브 클래스가 알고리즘의 특정 단계를 제공해야만 한다면 추상 메소드를 사용합니다.
알고리즘의 특정 단계가 선택적으로 적용된다면 후크를 사용합니다.

 

디자인 원칙 : 할리우드 원칙

먼저 연락하지 마세요. 저희가 연락 드리겠습니다.(Don't call us, we will call you.)

 

할리우드 원칙을 적용하면, 저수준 구성 요소에서 고수준 구성 요소를 직접 호출할 수 없게 하고, 고수준 구성 요소가 저수준 구성 요소를 직접 호출 하는 것은 허용합니다.

(할리우드에서 면접관이 “먼저 연락하지 마세요. 연락 드릴게요”라고 하는 것과 같아서 이름이 붙여졌다고 합니다.)

저수준 구성 요소에서 고수준 구성 요소를 직접 호출할 수 없게 하여 의존성 부패(dependency rot) 를 방지할 수 있습니다.

 

의존성 부패란?

고수준 구성 요소가 저수준 구성 요소에 의존하고, 저수준 구성 요소는 고수준 구성 요소에 의존하고 그 고수준 구성 요소는 다시 또 다른 구성 요소에 의존하는 식으로 의존성이 복잡하게 꼬여있는 것을 의존성 부패라고 한다.

 

또한 템플릿 디자인 패턴은 제어의 역전(Inversion of Control)의 한 예시입니다.

이는 고수준 코드가 런타임에 어떤 알고리즘을 실행할지 더 이상 결정하지 않고, 대신에 낮은 수준의 알고리즘이 선택되는 것을 의미합니다.

위에 템플릿 메소드 패턴에서는 Abstract Class에서 템플릿 메소드(코드에서 prepareRecipe)는 알고리즘을 장악하고 있고, 일부 단계에 대한 구현이 필요할 때 서브 클래스를 불러냅니다. 서브 클래스들은 호출 당하기 전까지는 추상클래스를 직접 호출하지 못합니다.

참고 자료

https://github.com/IT-Book-Organization/HeadFirst-DesignPattern/blob/main/Chapter_08/README.md

https://en.wikipedia.org/wiki/Template_method_pattern

 

Template method pattern - Wikipedia

From Wikipedia, the free encyclopedia Behavioral design pattern in object-oriented programming In object-oriented programming, the template method is one of the behavioral design patterns identified by Gamma et al.[1] in the book Design Patterns. The templ

en.wikipedia.org