디자인패턴: 템플릿 메소드 패턴
예제 : 커피와 홍차 클래스
1. Coffee와 Tea 클래스
public class Coffee {
void prepareRecipe() {
boilWater(); // 물 끓이기
brewCoffeeGrinds(); // 원두 필터로 내리기
pourInCup(); // 컵에 따르기
addSugarAndMikl(); // 설탕과 우유 추가
}
}
public class Tea {
void prepareRecipe() {
boilWater(); // 물 끓이기
steepTeaBag(); // 티백 우리기
pourInCup(); // 컵에 따르기
addLemon(); // 레몬 추가
}
}
2. 추상화
(1) Coffee와 Tea 클래스 추상화

(2) prepareRecipe() 메소드 추상화
- CaffeineBeverage 추상 클래스
- prepareRecipe 메소드
- Tea와 Coffee 만들 때 동일한 prepareRecipe 메소드 사용
- 서브 클래스에서 해당 메소드를 오버라이드 하여 제멋대로 레시피 수정 할 수 없도록 final로 선언함
- brew 메소드와 addCondiments 메소드
- 우리기와 첨가물 추가는 Tea와 Coffe에서 서로 다른 방식으로 처리 하므로 추상메서드로 선언
- 두 메서드는 서브 클래스에서 알아서 하도록 함
- Tea와 Coffee 클래스
- CaffeineBeverage를 확장함 (
extends CaffeineBeverage) - 우리기(
brew()) 와 첨가물 추가(addCondiments())를 override하여 정의함
- CaffeineBeverage를 확장함 (
// 추상 클래스
public abstract class CaffeineBeverage {
// final로 선언
final void preapareRecipe() {
boilWater();
brea();
pourInCup();
addCondiments();
}
// 추상메서드 선언
abstract void brew();
abstract void addCondiments();
void boilWater() { System.out.println("물 끓이는 중"); }
void pourInCup() { System.out.println("컵에 따르는 중"); }
}
public class Tea extends CaffeineBeverage {
public void brew() { System.out.println("티백 우리는 중"); }
public void addCondiments() { System.out.println("레몬 추가 중"); }
}
public class Coffee extends CaffeineBeverage {
public void brew() { System.out.println("원두 필터로 내리기"); }
public void addCondiments() { System.out.println("설탕과 우유 추가 중"); }
}
템플릿 메소드 패턴
1. 템플릿 메소드 패턴 정의
템플릿 메소드 패턴
메소드에서 **알고리즘의 골격(템플릿)**을 정의하기 위한 것
알고리즘의 여러 단계 중 일부는 서브클래스에서 구현할 수 있음
즉, 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의하고 싶을 때 사용
2. 템플릿 메소드 패턴 구조
- AbstractClass: 추상클래스로 선언되어 실제 연산을 구현해주는 서브클래스를 만들어 사용해야 함
- templateMethod (템플릿 메소드)
- 서브 클래스에서 알고리즘의 각 단계를 함부로 건들 수 없도록 final로 선언함
- 템플릿 메소드에서 각 단계들을 순서대로 정의하는데, 각 단계는 메소드로 표현됨
- primitiveOperation (기본단계 메소드) : abstract를 선언했으므로 구상 서브 클래스에서 구현해야 함
- concreteOperation (구상단계 메소드)
- 구상 단계는 추상 클래스 내에서 정의됨
- final로 정의 되었기 때문에 서브 클래스에서 오버라이드 불가능
- 이 메소드는 템플릿 메소드에서 호출할 수도 있고, 서브클래스에서 호출해서 사용할 수도 있음
- hook (후크 메소드)
- 기본적으로 아무 것도 하지 않는 구상메소드를 정의할 수 있음
- 이런 메소드를 후크(hook) 메소드라고 부름
- 서브 클래스에서 오버라이드 할 수도 있지만, 반드시 그래야 하는 것은 아님
abstract class AbstractClass {
// 템필릿 메소드
final void tempalteMethod() {
primitiveOperation1();
primitiveOperation2();
concreteOperation();
hool();
}
// 기본 단계 메소드
abstract void primitiveOperation1();
abstract void primitiveOperation2();
// 구상 단계 메소드
final void concreteOperation() {
// concreteOperation 메소드 실제 구현
}
void hook() {}
}
3. 템플릿 메소드와 후크
(1) 후크(hook)
후크란?
추상클래스에서 선언되는 메소드긴 하지만 기본적으로 내용만 구현되어 있거나, 아무 코드도 들어있지 않은 메소드
후크를 활용하면 서브 클래스 입장에서는 다양한 위치에서 알고리즘에 끼어들 수 있음
후크를 사용하려면 서브 클래스에서 오버라이드 해야 함
- 후크 용도
- 알고리즘에서 필수적이지 않은 부분을 필요에 따라 서브 클래스에서 구현하든 말든 하도록 해야 하는 경우 후크 사용
- 서브 클래스에서 특정 단계의 알고리즘을 수행해야 하는 경우에는 추상 메소드를 사용함
- AbstractTemplate(추상템플릿)에 정의된 추상메소드는 ConcreteClass(구상 클래스)에서 반드시 정의해야 함 → TemplateMethod에서 요구하는 모든 단계들을 제공해야 함
- 하지만, 알고리즘의 특정 부분이 선택적으로 적용되어야 하는 경우 후크 사용함
- 템플릿 메소드에서 앞으로 일어날 일에 대해 서브 클래스에서 반응할 기회를 제공하기 위한 용도로 후크 사용
- ex) 내부적으로 어떤 목록을 재정렬한 후에 서브 클래스에서 어떤 작업을 수행해야 하는 경우
- justReOrderedList() 후크 메소드 만들어서 사용
- 서브 클래스에 추상클래스에서 진행되는 작업에 대한 결정을 내리는 기능을 부여하기 위한 용도로 후크 사용
- ex) 손님이 첨가물 추가여부에 따라 특정 메소드 호출해야 하는 경우
- customerWantsCondiments() 후크 메소드 만들어서 사용
- 알고리즘에서 필수적이지 않은 부분을 필요에 따라 서브 클래스에서 구현하든 말든 하도록 해야 하는 경우 후크 사용
- 추상 메소드와 후크 메소드
- 추상메소드가 너무 많으면 서브 클래스에서 일일이 구현하기 불편함
- 알고리즘을 너무 잘게 쪼개지 않는 것도 하나의 방법이지만, 큼직하게 추상 메소드를 구현시 유연성이 떨어짐
- 필수적이지 않은 부분은 추상클래스가 아닌 후크로 구현하면, 해당 추상 클래스의 서브 클래스 만들 때 부담이 적어짐
(2) 후크 활용 코드 예시
customerWantsCondiments()- 구상 메서드이며, 내부 로직은 true만 리턴할 뿐 별 내용 없는 기본 메소드를 구현함
- 이 메소드는 서브 클래스에서 필요에 따라 오버라이드 할 수 있는 메소드이므로 hook임
- 조건문
customerWantsCodiments()메서드에 의해 실행여부가 결정되는 조건문 추가됨- 손님이 첨가물 넣어달라고 했을 때만,
addCondiments()호출
public abstract class CaffeineBeverageWithHook {
void prepareReceipe() {
boilWater();
brew();
pourInCup();
if(customerWantsCodiments()) {
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
void boilWater() { System.out.println("물 끓이는 중"); }
void pourInCup() { System.out.println("컵에 따르는 중"); }
boolean customerWantsCondiments() { return true; }
}
- 후크 활용
- 후크를 사용하려면 서브 클래스에서 오버라이드 해야 함
- 예시에서는 음료에 첨가물을 추가할지 처리 여부를 결정하기 위한 용도로 후크를 사용함
public class Coffee extends CaffeineBeverageWithHook {
public void brew() { System.out.println("원두 필터로 내리기"); }
public void addCondiments() { System.out.println("설탕과 우유 추가 중"); }
// 후크를 오버라이드해서 원하는 기능을 집어 넣음
// 기능 : 손님의 첨가물 추가 여부 답변에 따라 true 또는 false를 리턴함
public boolean customerWantsCondiments() {
String answer = getUserInput();
if (answer.equals("yes")) { reuturn true; }
return false;
}
}