GoF Behavioral Pattern에 해당하는 Template Method Pattern에 대해서 알아보자.
GoF 책에서는 다음과 같이 패턴의 의도를 밝힌다.
객체의 연산에는 알고리즘의 뼈대만을 정의하고 각 단계에서 수행할 구체적 처리는 서브클래스 쪽으로 미룹니다.
알고리즘의 구조 자체는 그대로 놔둔 채 알고리즘 각 단계 처리를 서브클래스에서 재정의할 수 있게 합니다.
예시 코드
위의 코드를 보면 커피를 만드는 절차 그리고 차를 만드는 절차를 구현하고 있다.
커피나 차를 만드는 절차 중 boil과 pour, add는 똑같이 필요한 절차이다.
그렇다면 그 외에 행위들은 마실 것에 따라 행위가 달라지는데, 절차(뼈대)는 수퍼 클래스에서 추상화하고, 이러한 행위를 서브클래스에서 구체화하는 것이다.
그렇다면 상위 클래스부터 공통된 절차를 추상 클래스로 정의해보자.
public abstract class CaffeineBeverage {
// Template method
public final void prepareRecipe() {
boilWater();
brew(); // 서브클래스에서 구체화
pourInCup();
addCondiments(); // 서브클래스에서 구체화
}
abstract protected void brew();
abstract protected void addCondiments();
private void boilWater() {
System.out.println("Boiling water");
}
private void pourInCup() {
System.out.println("Pouring into cup");
}
}
다음과 같이 절차를 정의해놓고, 마실 것(커피/차)에 따라 달라지는 행위는 자식 클래스에서 구현할 수 있도록 abstract
로 설정했다.
여기서 절차를 정의해놓은 메서드 혹은 추상 메서드들을 사용하여 알고리즘을 정의하는 메서드를 템플릿 메서드라고 한다.
그리고 서브 클래스에서 위 메서드를 구현해보자.
public class Coffee extends CaffeineBeverage {
@Override
protected void brew() {
System.out.println("Dripping Coffee through filter");
}
@Override
protected void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
}
public class Tea extends CaffeineBeverage {
@Override
protected void brew() {
System.out.println("Steeping the tea");
}
@Override
protected void addCondiments() {
System.out.println("Add Lemon");
}
}
위와 같이 서브클래스에서 구체적인 행위를 정의하여 template method pattern을 만들어 보았다.
여기서 추가적인 기법과 template method를 사용할 때 주의해야 할 점들을 소개하고 마친다.
hook method
서브 클래스를 구현할 때 융통성을 발휘하기 위한 메서드이다.
기본적인 내용만 구현되어 있거나 내용이 비어있다.
만약 내가 원할 때만 설탕 혹은 레몬을 추가하고 싶을 때 hook method를 사용하여 추가할 수 있다.
package Behavioral.templatemethod.after.beverage;
public abstract class CaffeineBeverage {
public final void prepareRecipe() {
boilWater();
brew(); // 서브클래스에서 구체화
pourInCup();// 서브클래스에서 구체화
if(customerWantsCondiments()) {
addCondiments();
}
}
abstract protected void brew();
abstract protected void addCondiments();
private void boilWater() {
System.out.println("Boiling water");
}
private void pourInCup() {
System.out.println("Pouring into cup");
}
// hook method
private boolean customerWantsCondiments() {
return true;
}
}
람다를 활용한 Template method pattern
구체화할 추상 메서드가 하나 뿐이라면 람다로 패턴에 적용할 수 있다.
다음과 같이 상점 오픈 전 준비를 한다고 가정한다.
public abstract class Shop {
public final void prepare() {
clean();
check();
}
private void clean() {
System.out.println("Cleaning");
}
protected abstract void check();
}
class Supermarket extends Shop {
@Override
protected void check() {
System.out.println("재고 확인");
}
}
이 때 추상 메서드가 하나이기 때문에 다음과 같이 리팩토링할 수 있다.
abstract
제거- 구현해야할 추상 메서드를 함수형 인터페이스를 인자로 만듦
public class Shop {
public final void prepare(Runnable doSomething) {
clean();
doSomething.run();
}
private void clean() {
System.out.println("Cleaning");
}
}
주의해야할 점
- Template method는 가능한 절제해야 한다.
기반 클래스는 변경될 가능성이 쉽기 때문에 깨지기가 쉽다.
그렇기 때문에 파생 클래스들로 커스터마이징이 많으면 많을수록 변경 점이 더욱 많아진다.
- 추상 메서드의 개수를 최소호화해야 한다.
추상 메서드가 많게 되면 서브 클래스를 구현하기 힘들고 개발자가 해당 메서드를 일일이 파악해야 한다.
- Template method의 불변성이 확실할 때에만
final
을 붙여 서브클래스가 오버라이드하는 것을 금지해야 한다.
Reference
- Head First Design Patterns
- 이종립님의 위키
틀린 점은 지적 부탁드립니다!
'Architecture' 카테고리의 다른 글
GoF Design Pattern - Proxy Pattern (0) | 2022.11.21 |
---|---|
GoF Design Pattern - Adapter Pattern (0) | 2022.11.15 |