템플릿 패턴

2018, May 26    

디자인 원칙

  • 헐리우드 원칙 : 먼저 연락하지 마세요. 저희가 연락 드리겠습니다.


템플릿 패턴은 어떤 작업 알고리즘의 골격(틀)을 정의하고 일부 변화되는 부분은 서브 클래스에서 구현하도록 하는 방식이다.
한 커피shop에 아래와 같이 Coffee와 Tea를 만드는 알고리즘의 두 클래스가 있다.

public class Coffee {
     void boilWater();              //물을끓이다.
     void brewCoffee();             //커피를 우린다.
     void pourInCup();              //컵에 따른다.
     void addSugarAndMilk();    //설탕과우유를 추가한다.
 }

 public class Tea {
     void boilWater();              //물을 끊이다.
     void brewTea();                    //차를 우린다.
     void pourInCup();              //컵에 따른다.
     void addLemon();               //레몬을 추가한다.

커피와 차를 만들어 내는 단계가 틀린듯 하면서도 몬가 비슷하다.
에이드를 만드는 알고리즘이 필요하다면 처음부터 똑같이 구현해야 할까?
아이스 커피를 만들어야 한다면? ‘음료’를 만드는 공통적인 부분을 추상화 하고, 개별적으로 달라지는 부분만 따로 구현할수 있도록 한다면 새로운 음료 알고리즘을 구현할때 훨신 수월할 것이다.
이렇게 공통 적인 부분을 추상화하여 템플릿(틀)을 만들고, 달라지는 부분만 서브 클래스에서 정의 할 수 있도록 하는 방식을 템플릿 패턴이라고 한다.

templateMethod()와 같이 어떤 알고리즘의 틀(템플릿)역할을 하는 메소드를 템플릿 메소드라고 한다.
템플릿 내에서 알고리즘의 각 단계는 메소드로 표현된다.

그렇다면 Coffee와 Tea의 템플릿을 만들기 위한 방법을 알아보자.

템플릿 패턴(템플릿 클래스를)을 만드는 방법

  • 로직의 공통적인 부분은 템플릿 메소드에 두고, 변경 될 수 없는 로직이라면 final 메소드로 선언한다.
  • 알고리즘 단계에 반드시 필요하지만 상황에 따라 변화되어야 하는 로직은 강제로 구현하게 하기 위해 추상 메소드로 선언한다.
  • Override해도 되고 안해도 되는 부분은 일반 메소드(Hook method)로 둔다.

Coffee와 Tea의 템플릿을 만들어 보자.
템플릿 클래스는 Coffee와 Tea를 추상화 하여 CaffeineBeverage(카페인음료)라고 하였다.

//1. 템플릿 메소드를 정의 하고, 공통적인 알고리즘 단계(메소드)를 추상화
public asbstract class CaffeineBeverage {

       //템플릿 메소드 : 커피와 차를 만드는 알고리즘의 틀(템플릿)에 해당하는 부분 정의
       void prepareRecipe() {
           boilWater();
           brew();
           pourInCup();
           addCondition();
       }

       void boilWater();
       void brew();             //메소드명은 달랐지만 공통적인 부분만 추출
       void pourInCup();
       void addCondition();  //메소드명은 달랐지만 공통적인 부분만 추출
}
/*
* 2. 변하지 않는 템플릿 메소드는 final로 선언하고,
* 반드시 구현되어야 하지만 변화가 필요한 알고리즘 단계의 메소드는
* 추상화로 선언한다.
*/
public asbstract class CaffeineBeverage {

       //변화지 않는 부분
       final void prepareRecipe() {
           boilWater();
           brew();
           pourInCup();
           addCondition();
       }

       void boilWater();
       abstract void brew();             //변화하는 부분
       void pourInCup();                
       abstract void addCondition();  //변화하는 부분
}public class Coffee extends CaffeineBeverage {void brew() {//커피를 우린다.}void addCondition() {
 //설탕과 우유를 첨가한다.
 }}public class Coffee extends CaffeineBeverage {
 void brew() {
 //커피를 우린다.
 }
 void addCondition() {
 //설탕과 우유를 첨가한다.
 }
}

필요하다면 hook mehod를 만들수도 있다.
Hook method : 추상 클래스에서 선언되는 메소드긴 하지만 기본적인 내용만 구현되어 있거나 아무 코드도 들어있지 않은 메소드.
이렇게 하면 서브 클래스 입장에서는 다양한 위치에서 알고리즘에 끼어 들 수 있다.

public abstract class CaffeinBeverag {
       final void prepareRecipe() {
           boilWater();
           brew();
           pourInCup();
           if(customerWantsCondiments()) {   //손님이 첨가물을 넣어달라고 할때만
              addCondition();
           }
       }

       void boilWater();
       abstract void brew();            
       void pourInCup();                
       abstract void addCondition();

      //첨가물을 넣을지 말지 선택하는 hook메소드
       boolean customerWantsCondiments() {
            return true;
       }
}

헐리우드 원칙과 템플릿 패턴

저 수준 구성요소를 사용하긴 하지만, 어떤식으로 쓰이는지는 고수준 구성요소에 의해 결정된다.


참조링크 : https://dzone.com/articles/the-hollywood-principle

  • CaffeinBeverage 추상클래스는 고수준의 구성 요소이다.
    음료를 만드는 방법에 해당하는 알고리즘을 장악하고 있고, 메소드 구현이 필요한 상황에서만 서브클래스를 부른다.
  • 서브클래스는 호출 "당하기"전까지는 절대로 추상클래스를 직접 호출 하지 않는다.
  • CaffeinBeverage 클래스의 client에서는 Tea나 Coffee같은 구상 클래스가 아닌 CaffeinBeverage,
    즉 추상화 되어 있는 부분에 의존하게 된다. 그렇게 함으로써 전체 시스템의 의존성이 줄어들 수 있다.