예제
public class Coffee{
void prepareRecipe(){
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void boidWater(){ System.out.println("물 끓이는 중"); }
public void brewCoffeeGrinds(){ System.out.println("커피 우려내는 중"); }
public void pourInCup(){ System.out.println("컵에 따르는 중"); }
public void addSugarAndMilk(){ System.out.println("설탕과 우유를 추가하는 중"); }
}
public class Tea{
void prepareRecipe(){
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
public void boidWater(){ System.out.println("물 끓이는 중"); }
public void steepTeaBag(){ System.out.println("차를 우려내는 중"); }
public void pourInCup(){ System.out.println("컵에 따르는 중"); }
public void addLemon(){ System.out.println("레몬을 추가하는 중"); }
}
public abstract class CaffeineBeverage {
public final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
protected abstract void addCondiments();
protected abstract void brew();
private void boilWater() {
System.out.println("물 끓이는 중");
}
private void pourInCup() {
System.out.println("컵에 따르는 중");
}
}
서브 클래스가 알고리즘 구조를 바꿀 수 없어야 한다. 따라서 오버라이드하지 못하도록 final로 선언했다
또한, 2,4번째 단계는 추상화를 통해 brew(), addCondiments()로 변경했다.
brew(), addCondiments()는 서브 클래스에서 처리한다. 따라서 추상 메서드로 선언했다.
public class Coffee extends CaffeineBeverage {
@Override
protected void brew() {
System.out.println("필터로 커피를 우려내는 중");
}
@Override
protected void addCondiments() {
System.out.println("설탕과 우류를 추가하는 중");
}
}
public class Tea extends CaffeineBeverage{
@Override
protected void brew() {
System.out.println("필터로 커피를 우려내는 중");
}
@Override
protected void addCondiments() {
System.out.println("설탕과 우류를 추가하는 중");
}
}
커피와 홍차를 만드는 방식을 일반화해서 알고리즘 구조(전체 처리 과정을 관리)를 base class에 넣었다.
이때, base class에서 1,3 단계는 직접 처리한다.
2,4 단계는 서브 클래스에 의존한다.
여기서 prepareRecipe()는 템플릿 메서드 입니다.
왜냐하면 어떤 알고리즘의 템플릿(틀) 여기서는 카페인 음료를 제조하는 알고리즘의 템플릿이다.
템플릿 내의 각 알고리즘 단계는 메소드로 표현된다.
어떤 메서드는 이 클래스에서 처리되기도 하고
서브 클래스에서 처리되는 메서드도 있다.
이때, 서브 클래스에서 구현해야 하는 메서드는 abstract로 선언해야 한다.
후크는 abstract 클래스에서 구현된 메서드이며, 아무런 기능이 없는 메서드이다.
따라서 서브 클래스에서 꼭 구현하지 않아도 되는 메서드이며
구현한다면 상황에 따라 알고리즘 진행을 다르게 할 수 있다는 장점이 있다.
//서브 클래스에서 템플릿(틀)을 건드리지 못하게 final 로 선언
abstract class AbstractClass{
final void templateMethod(){
//템플릿 메서드의 각 단계는 메서드로 표현된다.
primitiveOperation1();
primitiveOperation2();
concreateOperation();
hook();
}
//abstract로 선언된 메서드는 서브 클래스에서 구현해야 한다.
abstract void primitiveOperation1();
abstract void primitiveOperation2();
//final로 선언되었으므로 서브 클래스에서 오버라이드 할 수 없다.
final void concreateOperation(){
//코드
}
//아무것도 하지 않는 구상 메서드
//서브 클래스에서 오버라이드할 수 있지만, 반드시 그래야 하는 메서드는 아니다.
void hook(){ }
}
public abstract class CaffeineBeverage {
public void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
}
protected abstract void addCondiments();
protected abstract void brew();
private void boilWater() {
System.out.println("물 끓이는 중");
}
private void pourInCup() {
System.out.println("컵에 따르는 중");
}
//추상 클래스내에서 구현되었지만, 별 내용이 없는 메서드
//서브 클래스는 hook를 오버라이드할 수도 있고, 그냥 무시하고 넘어갈 수도 있다.
private boolean customerWantsCondiments() {
return true;
}
}
public class CoffeeWithHook extends CaffeineBeverage { // 후크를 오버라이드해서 원하는 기능을 추가
@Override
protected void brew() {
System.out.println("필터로 커피를 우려내는 중");
}
@Override
protected void addCondiments() {
System.out.println("설탕과 우류를 추가하는 중");
}
//기본적으로는 yes여서 첨가물을 넣지만
//서브 클래스에서 오버라이드하여 손님에게 물어보는 방식으로 기능구현했다.
@Override
protected boolean customerWantsCondiments() {
String answer = getUserInput();
if (answer.toLowerCase().startsWith("y")) {
return true;
}
return false;
}
private String getUserInput() {
String answer = null;
System.out.println("커피에 우유와 설탕을 넣을까요? (y/n)? ");
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
try {
answer = br.readLine();
} catch (IOException exception) {
System.out.println("io exception");
}
if (answer == null) {
return "no";
}
return answer;
}
}
public class BeverageTestDrive {
public static void main(String[] args) {
CoffeeWithHook coffeeWithHook = new CoffeeWithHook();
System.out.println("커피 준비 중");
coffeeWithHook.prepareRecipe();
}
}
===================================================================
커피 준비 중
물 끓이는 중
필터로 커피를 우려내는 중
컵에 따르는 중
커피에 우유와 설탕을 넣을까요? (y/n)?
y
설탕과 우류를 추가하는 중
Q : 템플릿 메서드 패턴에서 추상 메서드/후크 를 각각 어느 때 사용해야 하나요?
A :
Q : 추상 메서드가 너무 만아지면 서브 클래스에서 일일이 추상 메서드를 구현해야 하므로 좋지 않을 것 같다.
A : 따라서 알고리즘의 단계를 쪼개는 기준을 잘 생각해봐야 한다.
또한 필수가 아닌 부분을 후크로 구현하면 그 추상 클래스의 서브 클래스를 만들 때 무조건 해당 부분을 구현하는 게 아니므로 부담이 줄어든다.
할리우드 원칙을 활용하면 의존성 부패를 방지할 수 있다.
할리우드 원칙을 사용하면, 저수준 구성 요소가 시스템에 접속할 수는 있지만 어떻게 그 구성 요소를 사용할지는 고수준 구성 요소가 결정한다.
의존성 부패란?
의존성이 복잡하게 꼬여있는 상황으로 고수준 구성 요소가 저수준 구성 요소에 의존하고 저수준 구성 요소가 고수준 구성 요소에 의존(이하 순환 의존성)하면 시스템 디자인을 파악하기 힘들다.
템플릿 메서드 패턴은 프레임워크를 만드는 데 휼룡한 디자인 패턴이다.
프레임워크로 작업이 처리되는 방식을 제어할 수도 있으면서(추상 클래스에서 알고리즘 틀을 정의) 프레임워크에서 처리하는 알고리즘의 각 단계를 사용자가 정의할 수 있기 때문이다.(서브 클래스에서 abstract, 후크를 정의)