예제부터
public class Coffee {
void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void boilWater() {
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 boilWater() {
System.out.println("물 끓이는 중");
}
public void steepTeaBag() {
System.out.println("차를 우려내는 중");
}
public void pourInCup() {
System.out.println("컵에 따르는 중");
}
public void addLemon() {
System.out.println("레몬을 추가하는 중");
}
}
템플릿 메소드 패턴(Template Method Pattern)의 정의
- 템플릿 메소드 패턴(Template Method Pattern)에서는 알고리즘의 골격을 정의합니다. 알고리즘의 여러 단계 중 일부는 서브클래스에서 구현할 수 있습니다. 템플릿 메소드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의할 수 있습니다.
용어
- 템플릿 메소드
- 필수 처리 절차를 정의한 메소드.
- 서브클래스가 오버라이드하는 추상 메소드들을 사용하여 알고리즘을 정의하는 메소드.
- 훅 연산(hook operation)
- 필요하다면 서브클래스에서 확장할 수 있는 기본적인 행동을 제공하는 연산(메소드).
- 기본적으로는 아무 내용도 정의하지 않는다.
- 헐리우드 원칙
- 먼저 연락하지 마세요. 저희가 연락 드리겠습니다 .
- 헐리우드 원칙을 활용하면 "의존성 부패(dependency rot)"를 방지할 수 있습니다. 의존성이 복잡하게 꼬여있는 것을 의존성 부패라고 부릅니다. 의존성이 부패되면 시스템이 어떤 식으로 디자인된 것인지 거의 아무도 알아볼 수 없게 됩니다. 템플릿 메소드 패턴은 헐리우드 원칙을 적용한 패턴이라고 할 수 있습니다.
기본 구조
- 알고리즘은 final로 지정하여 서브클래스에서 수정할 수 없도록 하였습니다.
- 알고리즘의 일부 구현은 서브클래스에서 담당하도록 합니다.
- hook() 다양한 용도로 사용할 수 있습니다.
public abstract class AbstractClass {
final void templateMethod() {
primitiveOperation1();
primitiveOperation2();
concreteOperation();
hook();
}
abstract void primitiveOperation1();
abstract void primitiveOperation2();
final void concreteOperation() {}
void hook() {}
}
예제 수정
공통적인 부분을 뽑아 추상 클래스를 만든다.
- 알고리즘의 세부 항목에서 차이가 있는 곳은 추상 메소드로 정의한다.
public abstract class CaffeineBeverage {
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
abstract void brew();
abstract void addCondiments();
void boilWater() {
System.out.println("물 끓이는 중");
}
void pourInCup() {
System.out.println("컵에 따르는 중");
}
}
public class Coffee extends CaffeineBeverage {
public void brew() {
System.out.println("필터로 커피를 우려내는 중");
}
public void addCondiments() {
System.out.println("설탕과 커피를 추가하는 중");
}
}
public class Tea extends CaffeineBeverage {
public void brew() {
System.out.println("차를 우려내는 중");
}
public void addCondiments() {
System.out.println("레몬을 추가하는 중");
}
}
hook을 더하면...
public abstract class CaffeineBeverage {
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
void boilWater() {
System.out.println("물 끓이는 중");
}
void pourInCup() {
System.out.println("컵에 따르는 중");
}
boolean customerWantsCondiments() {
return true;
}
}
추가 참조 예제
기계인간 블로그
장단점
장점
- 큰 알고리즘에서 수정이 필요한 경우, 해당하는 부분에 대해서만 수정하도록 할 수 있으므로, 그 외 부분의 수정에 대한 영향도를 적게 가져갈 수 있음
단점
- 수정 가능 범위를 좁혀야 하므로, 일부 클라이언트에게 맞지 않을 수 있음.
Liskov Substitution Principle
를 위반할 여지가 충분함.
- 상위 객체의 뼈대가 중요하기 때문
- 만약 알고리즘 뼈대에서 추가 단계를 삽입하고자 한다면, 다른 디자인 패턴에 비해 품이 많이 들어갈 수 있음.
생각해볼문제
- 문서 추출 및 파싱
- Doc, CSV, pdf, hwp, txt 파일에 대해 문자열 추출 및 파싱을 진행해야 함
- 문서를 여는 방법, 파싱 마무리된 데이터 분석, 파일 전송(보고), 파일 닫기 로직은 동일
- 파일에서 문서 추출하는 방법, 추출된 데이터를 파싱하는 방법은 파일 형식마다 다름
- hwp 파일의 경우, 별도의 방법을 사용하고 있어 파싱 마무리 후 데이터 분석 전 별도로 인코딩을 진행해주어야 함
- 순서는 파일 열기
-> 파일에서 문서 추출
-> 추출된 문서 파싱
-> 파싱된 데이터 분석
-> 보고
-> 파일 닫기
순으로 진행