템플릿 메소드 패턴(Template Method Pattern)
알고리즘의 틀을 정의하는 패턴입니다. 이 패턴을 사용하면 알고리즘의 일부 단계를 서브 클래스에서 구현하거나 재정의해서 사용할 수 있습니다.
☕ 커피와 홍차에는 모두 카페인이 들어있다
스타버즈 커피 전문점에는 영업 비밀이 담긴 커피와 홍차를 제조하는 방법이 있는데, 이를 코드로 구현해보려고 합니다.
스타버즈 커피의 커피와 홍차 제조법은 아래와 같습니다.
커피 만드는 법
1. 물을 끓인다.
2. 끓는 물에 커피를 우려낸다.
3. 커피를 컵에 따른다.
4. 설탕과 우유를 추가한다.
홍차 만드는 법
1. 물을 끓인다.
2. 끓는 물에 찻잎을 우려낸다.
3. 홍차를 컵에 따른다.
4. 레몬을 추가한다.
위 제조법을 코드로 구현해보면 아래처럼 만들 수 있겠네요.
public class Coffee {
void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void boilWater() {
System.out.println("Boiling water");
}
public void brewCoffeeGrinds() {
System.out.println("Brewing coffee grinds");
}
public void pourInCup() {
System.out.println("Pouring cup");
}
public void addSugarAndMilk() {
System.out.println("Adding sugar and milk");
}
}
public class Tea {
void prepareRecipe() {
boilWater();
steepTeBag();
pourInCup();
addLemon();
}
public void boilWater() {
System.out.println("Boiling water");
}
public void steepTeBag() {
System.out.println("Steeping tebag");
}
public void pourInCup() {
System.out.println("Pouring cup");
}
public void addLemon() {
System.out.println("Adding lemon");
}
}
두 클래스를 만들다보면 아래 과정이 완전히 동일하다는 것을 알 수 있습니다.
- 물을 끓인다.
- 음료를 컵에 따른다.
이 과정을 각 클래스가 구현하고 있기 때문에 코드가 중복되고 있는 것을 알 수 있습니다.
이러한 코드의 중복을 없애는 방법을 생각해보면.. 상위 클래스에서 공통 부분을 상속받아 사용하는 것을 생각할 수 있겠네요.
위를 코드로 구현해보면 아래처럼 만들 수 있겠네요.
public abstract class CaffeinBeverage {
// 이 메소드는 서브 클래스에서 정의하도록 추상 메소드로 선언했습니다.
abstract void prepareRecipe();
// 서브 클래스가 오버라이드 하지 못하게 final로 선언했습니다.
final void boilWater() {
System.out.println("Boiling water");
}
final void pourInCup() {
System.out.println("Pouring in cup");
}
}
public class Coffee extends CaffeinBeverage {
void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void brewCoffeeGrinds() {
System.out.println("Brewing coffiee grinds");
}
public void addSugarAndMilk() {
System.out.println("Adding sugar and milk");
}
}
public class Tea extends CaffeinBeverage {
void prepareRecipe() {
boilWater();
steepTeBag();
pourInCup();
addLemon();
}
public void steepTeBag() {
System.out.println("Steeping tebag");
}
public void addLemon() {
System.out.println("Adding lemon");
}
}
다시 구현하고 각 서브 클래스에 메소드들을 다시 보니 완전히 같지는 않지만 둘 다 무언가를 우려 내고, 첨가물을 넣는다는 점이 비슷하네요. 전체적인 과정도 아래처럼 공통화시킬 수 있을 것 같습니다.
- 물을 끓인다.
- 무언가를 우려 낸다.
- 음료를 컵에 따른다.
- 첨가물을 음료에 추가한다.
public abstract class CaffeinBeverage {
// 상위 클래스에서 제조법을 공통화시켰습니다.
void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
// 서로 다른 부분은 서브 클래스에서 구현하도록 추상 메소드로 설정했습니다.
public abstract void brew();
public abstract void addCondiments();
final void boilWater() {
System.out.println("Boiling water");
}
final void pourInCup() {
System.out.println("Pouring in cup");
}
}
public class Coffee extends CaffeinBeverage {
public void brew() {
System.out.println("Brewing coffiee grinds");
}
public void addCondiments() {
System.out.println("Adding sugar and milk");
}
}
public class Tea extends CaffeinBeverage {
public void brew() {
System.out.println("Steeping tebag");
}
public void addCondiments() {
System.out.println("Adding lemon");
}
}
위와 같이, 일련의 과정을 상위 클래스에서 정의하고 일부 단계를 서브 클래스가 구현하도록 유도하는 패턴을 템플릿 메소드 패턴이라고 합니다.
템플릿 메소드 패턴은 말 그대로 일련의 과정을 템플릿(틀)으로 만들고, 여러 단계 중 하나 이상의 단계를 추상 메소드로 만들어서 서브 클래스가 이를 구현하도록 유도함으로써, 서브 클래스가 일부분 구현을 처리하게 하면서도 알고리즘의 구조를 변경하지 않아도 되는 장점이 있습니다.
템플릿 메소드 패턴에는 아래와 같은 방법도 있습니다.
public abstract class CaffeinBeverage {
// 상위 클래스에서 제조법을 공통화시켰습니다.
void prepareRecipe() {
boilWater();
brew();
pourInCup();
// 기본적으로 첨가물을 넣지만, 손님 요구에 따라 넣지 않습니다.
if (customerWantsCondiments()) {
addCondiments();
}
// 손님으로부터 다른 요청 사항이 있을지도 모르죠?
additionalAction();
}
// 이 메소드는 서브 클래스에서 선택적으로 재정의해서 알고리즘 단계를 분기시키고 있습니다.
public boolean customerWantsCondiments() {
return true;
}
// 이 메소드는 아무것도 하지 않지만, 필요 시 서브 클래스에서 재정의해서 알고리즘 단계에 로직을 추가할 수 있습니다.
public void additionalAction() {}
...
}
public Coffee extends CaffeinBeverage {
...
public boolean customerWantsCondiments() {
return false;
}
}
위와 같이, 추상 클래스에서 customerWantsCondiments()와 additionalAction()과 같은 메소드를 후크(hook)라고 합니다.
후크는 다양한 목적으로 사용될 수 있고, 서브 클래스가 선택적으로 오버라이드 해서 알고리즘 단계에 끼어들게 할 수 있는 것이 특징입니다.
✅ 할리우드 원칙
먼저 연락하지 마세요. 저희가 연락드릴게요.
할리우드 원칙을 사용하면 의존성 부패(dependency rot)를 방지할 수 있습니다.
의존성 부패라는 것은 고수준 구성 요소가 저수준 구성 요소를 의존하고, 저수준 구성 요소가 다시 고수준 구성 요소를 의존하고, 다른 구성 요소는 저수준 구성 요소를 의존하는 등 의존성이 복잡하게 꼬여 있는 상황을 말합니다.
할리우드 원칙은 저수준 구성 요소가 고수준 구성 요소에 접속할 수는 있지만, 그 구성 요소를 언제 어떻게 사용할지는 오직 고수준 구성 요소가 결정하게 되므로, 객체 간의 의존성을 줄일 수 있는 원칙 중 하나입니다.
이러한 할리우드 원칙은 템플릿 메소드 패턴에서도 확인할 수 있는데요, 위 예시 코드에서 CaffeinBeverage 클래스는 고수준 구성요소이고, Coffee와 Tea 클래스는 저수준 구성요소라고 할 수 있습니다.
객체 간 의존성을 줄이는 다른 원칙인 의존성 뒤집기 원칙과 유사하지만, 의존성 뒤집기 원칙이 훨씬 강하고 일반적인 내용의 원칙입니다.
Java에서는 이러한 템플릿 메소드 패턴이 Arrays.sort() 메소드에서 사용되는 것을 확인할 수 있습니다.
String[] test = { "2", "1"};
// Arrays에서 기본적으로 제공하는 비교 로직을 사용합니다.
Arrays.sort(test);
// 필요 시 오버라이드를 통해 정렬 과정에 직접 참여합니다.
Arrays.sort(test, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
// 비교 단계를 직접 구현해서 정렬합니다.
}
});
템플릿 메소드 패턴에서 배울 수 있는 객체지향 원칙
- 바뀌는 부분은 캡슐화한다.
- 상속보다는 구성을 활용한다.
- 구현보다는 인터페이스에 맞춰서 프로그래밍한다.
- 상호작용하는 개체 사이에서는 가능하면 느슨한 결합을 사용해야 한다.
- 클래스는 확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 한다(OCP).
- 추상화된 것에 의존하게 만들고 구상 클래스에 의존하지 않게 만든다.
- 먼저 연락하지 마세요. 저희가 연락드리겠습니다(할리우드 원칙).
템플릿 메소드 패턴(Template Method Pattern)
알고리즘의 틀을 정의하는 패턴입니다. 이 패턴을 사용하면 알고리즘의 일부 단계를 서브 클래스에서 구현하거나 재정의해서 사용할 수 있습니다.
'Fundamental > Design Pattern' 카테고리의 다른 글
[디자인 패턴] 9. 반복자 패턴, 컴포지트 패턴 (0) | 2024.11.03 |
---|---|
[디자인 패턴] 7. 어댑터 패턴, 퍼사드 패턴 (1) | 2024.10.04 |
[디자인 패턴] 6. 커맨드 패턴(Command Pattern) (0) | 2024.09.29 |
[디자인 패턴] 5. 싱글턴 패턴(Singleton Pattern) (0) | 2024.09.20 |
[디자인 패턴] 4. 팩토리 패턴(Factory Pattern) (0) | 2024.09.10 |