본문 바로가기
OOP/<헤드 퍼스트 디자인 패턴>, 에릭 프리먼 외

Template Method

by 민휘 2023. 2. 24.

핵심 의도

알고리즘의 구조는 유지하면서 알고리즘의 특정 단계를 서브클래스에서 구현하거나 재정의하는 방법이다.

 

적용 상황

코드의 유사성이 높은데 코드의 일부분이 다른 경우, 템플릿 메소드로 공통인 부분은 상위로 올리고 다른 부분만 서브 클래스에서 구현하게 한다. 코드가 그대로 중복되는 것을 막을 수 있고, 공통 인터페이스를 사용해 메시지를 요청할 수 있다.

프레임워크를 제공할 때 자주 사용한다. 프레임워크는 사용자로 하여금 작업 흐름을 강제하는데, 템플릿 메소드에서 알고리즘 구조를 제어할 수 있다. 그러면서도 알고리즘의 각 단계는 사용자가 마음대로 지정할 수 있다.

 

솔루션의 구조와 각 요소의 역할

객체에게 책임을 분할하기

일련의 알고리즘을 정의한 메소드인 템플릿 메소드를 가진 상위 클래스가 필요하다. 템플릿 메소드에서 호출하는 알고리즘의 각 단계를 구현하는 서브 클래스도 필요하다.

 

구현 포인트

💡 템플릿 메소드에서 호출하는 메소드
  1. 모든 서브 클래스에서 공통으로 사용하는 메소드
  2. 각 서브 클래스에서 구현할 추상 메소드
  3. 서브 클래스에서 오버라이딩할 수 있는 후크 메소드

 

공통 메소드는 상위 클래스에서 구현해야하며, 외부의 메시지에 응답할 때 이 공통 메소드는 상위 클래스에서 처리한다. 반면 추상 메소드는 서브 클래스에 의존하므로 이 동작은 서브 클래스에서 처리한다.

후크는 다양한 용도로 사용할 수 있는데, 알고리즘의 특정 단계가 선택적으로 적용되는 경우 후크를 상위 클래스에 둔다. 알고리즘에서 필수적이지 않은 부분을 서브클래스에서 구현하도록 만들고 싶을 때, 서브클래스가 알고리즘의 각 단계를 처리할지 말지 결정하게 하고 싶을 때, 템플릿 메소드에서 일어나는 일에 대해 서브 클래스가 반응할 수 있도록 하고 싶을 때 사용한다.

 

💡 할리우드 원칙 - 먼저 연락하지 마세요. 저희가 연락 드리겠습니다

이 원칙을 사용하면 저수준 구성 요소가 고수준 구성 요소에 접속할 수는 있지만, 그 구성 요소를 어떻게 사용할지는 고수준 구성 요소가 결정한다. 이렇게 하면 수준별로 의존성이 꼬이는 문제를 막을 수 있다.

템플릿 메소드 패턴을 사용하면 할리우드 원칙을 지키게 된다. 알고리즘을 실행할 때 서브 클래스에 의존하고 있는 메소드를 호출하게 되면 그때 상위 클래스가 서브 클래스에게 메시지를 전송하기 때문이다.

 

💡 템플릿 메소드 패턴 vs 전략 패턴 vs 팩토리 메소드 패턴
  • 템플릿 메소드 : 알고리즘의 어떤 단계를 구현하는 방법을 서브클래스에서 결정한다. 상속을 사용한다.
  • 전략 패턴 : 바꿔 쓸 수 있는 행동을 캡슐화하고, 어떤 행동을 사용할지는 서브클래스에게 맡긴다. 구성을 사용한다.
  • 팩토리 메소드 : 생성이라는 알고리즘에 집중한 템플릿 메소드 패턴이다.

 

적용 예시

 

요구사항

커피와 홍차를 제조한다. 커피는 물을 끓이고, 커피를 우려내고, 커피를 컵에 따르고, 설탕과 우유를 추가한다. 홍차는 물을 끓이고, 찻잎을 우려내고, 홍차를 컵에 따르고, 레몬을 추가한다. 두 종류의 음료를 제조하는 코드의 중복을 제거해보자.

 

설계

prepareRecipe()는 템플릿 메소드이다. 이 템플릿 메소드는 CaffeinBeverage를 상속하는 모든 서브 클래스 음료의 제조 알고리즘을 제어한다. 나머지 네개의 메소드는 템플릿 메소드에서 호출되는 각 단계의 알고리즘이며, brew()와 addCondiments()는 서브 클래스에서 각자 구현한다.

 

코드

CaffeinBeverage

public abstract class CaffeineBeverageWithHook {
 
	final void prepareRecipe() {
		boilWater();
		brew();
		pourInCup();
		if (customerWantsCondiments()) {
			addCondiments();
		}
	}
 
	abstract void brew();
 
	abstract void addCondiments();
 
	void boilWater() {
		System.out.println("Boiling water");
	}
 
	void pourInCup() {
		System.out.println("Pouring into cup");
	}
 
	boolean customerWantsCondiments() {
		return true;
	}
}

Coffee

public class Coffee extends CaffeineBeverage {
	public void brew() {
		System.out.println("Dripping Coffee through filter");
	}
	public void addCondiments() {
		System.out.println("Adding Sugar and Milk");
	}
}

Tea

public class Tea extends CaffeineBeverage {
	public void brew() {
		System.out.println("Steeping the tea");
	}
	public void addCondiments() {
		System.out.println("Adding Lemon");
	}
}

BeverageTest

public class BeverageTestDrive {
	public static void main(String[] args) {
 
		Tea tea = new Tea();
		Coffee coffee = new Coffee();
 
		System.out.println("\\nMaking tea...");
		tea.prepareRecipe();
 
		System.out.println("\\nMaking coffee...");
		coffee.prepareRecipe();

 
		TeaWithHook teaHook = new TeaWithHook();
		CoffeeWithHook coffeeHook = new CoffeeWithHook();
 
		System.out.println("\\nMaking tea...");
		teaHook.prepareRecipe();
 
		System.out.println("\\nMaking coffee...");
		coffeeHook.prepareRecipe();
	}
}

 

'OOP > <헤드 퍼스트 디자인 패턴>, 에릭 프리먼 외' 카테고리의 다른 글

Composite  (0) 2023.02.24
Iterator  (0) 2023.02.24
Facade  (0) 2023.02.24
Adapter  (0) 2023.02.24
Command  (0) 2023.02.24