9. Template Method Pattern(템플릿 메소드 패턴)
9.1 Template Method Pattern 이란
이 패턴은 알고리즘의 틀(템플릿)을 만들기 위한 패턴입니다.
방식은 아래와 같습니다.
- 추상 클래스에서 알고리즘의 골격을 담당함 메소드를 정의합니다.
- 알고리즘의 단계를 담당할 메소드들을 정의합니다.
- 알고리즘의 단계 중 서브클래스에서 구현하고 싶은 부분은 추상 메소드로 정의합니다.
9.2 Template Method Pattern의 장점
이런식으로 알고리즘을 캡슐화 하면 여러 장점이 있습니다.
- 알고리즘의 일부 단계를 서브클래스 마다 다르게 구현할 수 있다
- 알고리즘의 공통부분을 정의하는 코드를 서브클래스들에서 상속을 통해 코드의 재사용이 가능합니다.
- 알고리즘을 수정할 일이 발생하면 모든 서브클래스를 수정할 필요없이 알고리즘을 정의한 클래스만 수정하면 됩니다.
9.3 Template Method Pattern 다이어그램
9.4 간단한 Template Method Pattern 예제
커피와 차는 둘다 마시는 음료입니다. 커피와 차를 만드는 방법은 거의 비슷합니다.
1) 물을 끓이고
2) 원료를 우려내고
3) 컵에 따른 후
4) 취향에 따라 추가재료를 넣죠
이렇게 두 음료는 같은 알고리즘을 가집니다.
9.4.1 Abstract class
알고리즘이 캡슐화되어 있는 클래스입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public abstract class CaffeineBeverage { final void prepareRecipe() { boilWater(); brew(); pourInCup(); addCondiments(); } public void boilWater() { System.out.println("물을 끓입니다."); } public void pourInCup() { System.out.println("컵에 음료를 따릅니다."); } abstract void brew(); abstract void addCondiments(); } | cs |
9.4.2 Concrete class
알고리즘의 어느 단계를 재정의하여 사용하는 서브클래스입니다.
| public class Coffee extends CaffeineBeverage { @Override void brew() { System.out.println("커피를 우려냅니다."); } @Override void addCondiments() { System.out.println("설탕과 우유를 추가합니다."); } } | cs |
| public class Tea extends CaffeineBeverage { @Override void brew() { System.out.println("차를 우려냅니다."); } @Override void addCondiments() { System.out.println("레몬을 추가합니다."); } } | cs |
9.5 Hook
hook는 기본적인 코드만 들어가 있거나 아무 코드도 들어있지 않은 메소드입니다.
hook 메소드는 다양한 용도로 사용될 수 있습니다.
이러한 메소드를 정의해 놓으면 서브클래스에서는 hook를 재정의해서 알고리즘에 간섭할 수 있습니다.
9.6 Hook 적용 예
차에는 레몬이 반드시 들어가지만, 커피에는 설탕과 우유를 손님이 원할때만 추가하고 싶습니다.
hook를 이용해 보도록 하죠.
9.6.1 Abstract class
알고리즘이 캡슐화되어 있는 클래스입니다.
서브클래스에서는 hook메소드를 재정의해 알고리즘에 간섭할 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | public abstract class CaffeineBeverage { // 알고리즘 final void prepareRecipe() { boilWater(); brew(); pourInCup(); if(customerWantsCondiments()) { addCondiments(); } } public void boilWater() { System.out.println("물을 끓입니다."); } public void pourInCup() { System.out.println("컵에 음료를 따릅니다."); } abstract void brew(); abstract void addCondiments(); // hook public boolean customerWantsCondiments() { return true; } } | cs |
9.6.2 Concrete class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | import java.util.Scanner; public class Coffee extends CaffeineBeverage { @Override void brew() { System.out.println("커피를 우려냅니다."); } @Override void addCondiments() { System.out.println("설탕과 우유를 추가합니다."); } // hook 재정의 public boolean customerWantsCondiments() { String answer = getUserInput(); if(answer.equals("y")){ return true; } else { return false; } } public String getUserInput() { Scanner scanner = new Scanner(System.in); System.out.println("커피에 우유와 설탕을 넣어 드릴까요? (y/n)"); String answer = scanner.nextLine(); return answer; } } | cs |
| public class Tea extends CaffeineBeverage { @Override void brew() { System.out.println("차를 우려냅니다."); } @Override void addCondiments() { System.out.println("레몬을 추가합니다."); } } | cs |
9.6.3 Test class
| public class BeverageTestDrive { public static void main(String args[]) { Coffee coffee = new Coffee(); Tea tea = new Tea(); coffee.prepareRecipe(); System.out.println("---------------------------"); tea.prepareRecipe(); } } | cs |