Design Pattern

10. Template Method Pattern

kangwook 2022. 8. 29. 21:32

어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화해 전체 일을 수행하는 구조는 바꾸지 않으면서 특정 단계에서 수행하는 내역을 바꾸는 패턴

Template Method Pattern

Template Method 패턴은 메소드에서 알고리즘의 뼈대를 정의하고 일부 단계를 하위 클래스로 미룬다. 즉, Template Method를 사용하면 알고리즘의 구조를 변경하지 않고 알고리즘의 특정 단계를 다시 정의할 수 있다.

  • Abstract Class
    • 템플릿 메서드를 정의하는 클래스
    • 하위 클래스에 공통 알고리즘을 정의하고 하위 클래스에서 구현될 기능을 primitive 메서드로 정의하는 클래스
  • ConcreteClass
    • 물려받은 primitive 메서드를 구현하는 클래스
    • 상위 클래스에 구현된 템플릿 메서드의 일반적인 알고리즘에서 하위 클래스에 적합하게 primitive 메서드를 오버라이드 하는 클래스

Template Method 특징

  • 동일한 기능을 상위 클래스에서 정의하면서 확장 / 변화가 필요한 부분만 서브 클래스에서 구현할 수 있도록 함
  • 즉, 전체적인 알고리즘은 상위 클래스에서 구현하면서 다른 부분은 하위 클래스에서 구현함으로써 알고리즘 코드를 재사용하는데 유용

Template Method Pattern 예제

  • 커피와 차가 만들어지는 방법
  1. 커피 만드는 법
    1. 물을 끓인다.
    2. 끓는 물에 커피를 우려낸다.
    3. 커피를 컵에 따른다.
    4. 설탕과 우유를 추가한다.
  2. 차 만드는 법
    1. 물을 끓인다.
    2. 끓는 물에 차를 우려낸다.
    3. 차를 컵에 따른다.
    4. 레몬을 추가한다.
  • 커피와 차의 만드는 알고리즘이 거의 똑같다는 것을 알 수 있음
    1. 물을 끓인다.
    2. 뜨거운 물을 이용하여 커피 또는 차를 우려낸다.
    3. 만들어진 음료를 컵에 따른다.
    4. 각 음료에 맞는 첨가물을 추가한다.

  • AbstractClass : CaffeineBeverage 클래스
    • Template Method인 prepareRecipe() 클래스를 final로 선언
      • prepareRecipe() 메소드를 오버라이딩해서 바꾸면 안되기 때문HomeTheaterFacade 클래스에서 subsystem으로의 쓰기 쉬운 인텉페이스를 만듦
public abstract class CaffeineBeverage {
    void final prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }
     
    abstract void brew();
    abstract void addCondiments();
 
    void boilWater() {
        System.out.println("Boiling water");
    }
 
    void pourInCup() {
        System.out.println("Pouring into cup");
    }
}
  • ConcreteClass : Coffee, Tea 클래스
public class Coffee extends CaffeineBeverage {
    @Override
    public void brew() {
        System.out.println("Dripping Coffee through filter");
    }
 
    @Override
    public void add Condiments() {
        System.out.println("Adding Sugar and Milk");
    }
}
 
public class Tea extends CoffeineBeverage {
    @Override
    public void brew() {
        System.out.println("Steeping the tea)";
    }
         
    @Override
    public void addCondiments() {
        System.out.println("Adding lemon");
    }
}
 

Tea를 생성하는 과정

  • Tea 객체를 생성한다.
Tea myTea = new Tea();
  • 템플릿 메소드를 호출한다.
    • 음료를 만들기 위한 알고리즘이 실행
myTea.prepareRecipe();
  • 물을 끓인다.
    • CaffeineBeverage에서 처리됨
boilWater();
  • 차를 우려낸다.
    • 이 방법은 서브 클래스만 알고 있음
brew();
  • 차를 컵에 따른다
    • CaffeineBeverage에서 처리됨
pourInCup();
  • 마지막으로 첨가물을 추가한다.
    • 첨가물은 음료마다 다르기 때문에 서브클래스에서 처리됨
addCondiments();

후크(hook)

  • 후크는 추상클래스에서 선언되는 메소드이긴 하지만 기본적인 내용만 구현되어 있거나 아무 코드도 들어있지 않은 메소드
    • 아래 코드에서 customerWantsCondiments() 메소드는 서브클래스에서 필요에 따라 오버라이드 할 수 있는 메소드인 후크
    • 서브클래스에서 적절하게 오버라이드해서 사용하면 됨
public abstract class CaffeineBeverageWithHook {
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }
 
...
 
    boolean customerWantsCondiments() {
        return true;
    }
}
  • Coffee 클래스에서 사용하는 방법
public class CoffeeWithHook extends CaffeineBeverageWithHook {
    ...
    @Override
    public boolean customerWantsCondiments() {
        String answer = getUserInput();
        if(answer.toLowerCase().startWith("y"))
            return true;
        else
            return false;
    }
     
    private String getUserInput() {
        String answer = null;
        System.out.println("Would you like milk and sugar with your coffee (y/n)?");
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        try {
            answer = in.readLine();
        } catch (IOException ioe) {
            System.err.println("IO error trying to read your answer");
        }
        if (answer == null) {
            return "no";
        }
        return answer;
    }
}
  • 이처럼 알고리즘에서 필수적이지 않은 부분을 필요에 따라 선택적으로 서브클래스에서 구현하던 말던 하도록 하는 경우에 후크를 사용

결론

  • 작업의 일부분을 캡슐화하여 전체 일을 수행하는 구조는 그대로 유지하면서 특정 부분을 바꾸는 행동 패턴(Behavioral Pattern)
  • 장점
    • 중복 코드를 줄임으로써 핵심 로직의 관리가 용이
    • 확장이 쉬움 → 조금 더 객체 지향적인 설계를 할 수 있음 
  • 단점
    • 추상 메소드가 많아지면서 클래스 관리가 복잡해짐
    • 상위 클래스가 관리하는 하위 클래스가 많을 때, 상위 클래스를 수정하게 될 때 모든 하위 클래스들을 일일이 수정해야하는 상황이 발생 

 

 

 


본문은 Head First Design Pattern (2004) 를 참고하여 공부하고 작성하였음