반응형
템플릿 메소드 패턴 이해를 위한 간단한 문제 제안
- 우리는 개발을 하다보면 비슷한 메소드에 비슷한 로직을 작성할 때가 있습니다.
- 특히 아래와 같은 상황은 개발자들에게 최적화에 대한 욕망과 갈증을 뿜뿜하게 됩니다.
스타버즈 커피 만드는 법
- 물을 끓인다.
- 끓는 물에 커피를 우려낸다.
- 커피를 컵에 따른다.
- 설탕과 우유를 추가한다.
스타버즈 홍차 만드는 법
- 물을 끓인다.
- 끓는 물에 찻잎를 우려낸다.
- 홍차를 컵에 따른다.
- 레몬을 추가한다.
- 두 개의 레시피를 살펴보면 재료만 다르지 행동은 비슷한다는걸 알 수 있습니다.
- 따라서 위의 레시피를 문맥 그대로 코드로 녹여낼 경우 반복적인 코드가 탄생할 것입니다.
- 템플릿 메소드 패턴은 예시처럼 비슷한 로직을 템플릿화 하여 코드의 퀄리티를 높여주는 디자인 패턴입니다.
템플릿 메소드 패턴을 적용해보기
- 위 사진은 레시피 절차를 코드로 구현한것입니다.
- 코드를 보면 2번, 4번 줄을 제외하고는 절차와 로직이 완벽히 동일하다는것을 알 수 있습니다.
- 템플릿 메소드 패턴은 이와 같은 유사하지만 조금씩 다른 로직을 abstract로 설정하여 개발자가 변화되는 부분만 재정의 하도록 유도하는 패턴입니다.
public abstract class CaffeineBeverage {
final void prepareRecipe() { // 서브 클랠스가 메소드를 함부로 오버라이드 못하도록 final 선언
boilWater();
brew(); // 커피와 차 둘 다 사용할 수 있게 공통된 이름으로 변경
pourInCup();
addCondiments(); // 커피와 차 둘 다 사용할 수 있게 공통된 이름으로 변경
}
abstract void brew(); // 해당 템플릿 메소드 패턴을 사용하는 서브클래스는 brew를 정의해야함
abstract void addCondiments(); // 해당 템플릿 메소드 패턴을 사용하는 서브클래스는 addCondiments를 정의해야함
void boilWater() {
System.out.println("Boiling water");
}
void pourInCup() {
System.out.println("Pouring into cup");
}
}
- 여기서 부터는 템플릿 메소드 패턴의 템플릿을 직접 구현해보겠습니다.
- 먼저 위의 prepareRecipe 로직에서 2번,4번 줄의 이름을 공통적인 느낌을 주기 위해 변경하였습니다.
- 이외의 개발자가 해당 템플릿 메소드 패턴을 사용했을때 재정의 해야하는 영역은 absstract로 설정해줍니다.
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");
}
}
public class Tea extends CaffeineBeverage {
public void brew() {
System.out.println("Steeping the tea");
}
public void addCondiments() {
System.out.println("Adding Lemon");
}
}
- 서브클래스에서는 템플릿인 CaffeinBeverage를 재정의해서 사용하면 됩니다.
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();
}
}
- 마지막으로 테스트를 진행하도록 해보겠습니다.
수행결과
- 지금까지 정리하자면 템플릿 메소드 패턴을 사용할 때 핵심은 변화하는 부분을 abstract으로 설정하여 개발자가 변화하는 부분만 재정의하여 사용하도록 유도하는 디자인 패턴입니다.
- 이를 통해 템플릿 메소드는 알고리즘의 각 단계를 정의할 수 있고 서브클래스를 구현하는 개발자가 일부 단계를 구현할 수 있도록 유도할 수 있습니다.
템플릿 메소드 패턴의 장점
템플릿 메소드 패턴을 사용하지 않고 Coffe와 Tea를 둘 다 구현한 경우 | 템플릿 메소드로 만든 CaffeinBeverage를 사용한 경우 | |
관리 측면 | Coffe와 Tea 클래스 각각 관리해야 함 | CaffeinBeverage 클래스에서 작업을 처리함 |
중복 코드 | Coffe와 Tea 클래스에 중복 코드가 존재 합니다. | CaffeinBeverage 덕분에 서브클래스에서 코드를 재사용할 수 있습니다. |
수정 | 알고리즘 변경시 변경된 서브클래스를 전부 고쳐야함 | 알고리즘이 한 곳에 모여 있으므로 한 부분만 고치면 된다. |
확장성 | 낮음 | 높음 |
템플릿 매소드 페턴의 정의
- 템플릿 메소드 패턴은 알고리즘의 골격을 정의합니다.
- 간단히 말하면 템플릿 메소드 패턴은 알고리즘의 템플릿을 만드는것을 의미합니다.
- 템플릿 메소드를 사용하면 알고리즘의 일부 단계를 서브클래스에서 구현할 수 있고, 알고리즘의 구조는 그대로 유지하면서 알고리즘의 특정 단계를 서브 클래스에서 재정의할 수 있습니다.
템플릿 메소드의 후크 알아보기
- 후크(hook)는 추상 클래스에서 선언되지만 기본적인 내용만 구현되어 있거나 아무 코드도 들어 있지 않은 메소드입니다.
- 후크는 개발자가 원하는 다양한 용도로 사용할 수 있는 메소드입니다.
- 이를 통해 후크는 알고리즘 진행 순서를 변경할 수 있습니다.
public abstract class CaffeineBeverageWithHook {
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
//고객물이 첨가물 동의를 한 경우에만 첨가물을 넣게 코드를 변경. 만약 기본값을 false로 바꾸고 싶다면 hook를 재정의하면 된다.
if (customerWantsCondiments()) {
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
void boilWater() {
System.out.println("Boiling water");
}
void pourInCup() {
System.out.println("Pouring into cup");
}
//customerWantsCondiments는 Hook 메소드이다.
boolean customerWantsCondiments() {
return true;
}
}
- 위처럼 후크 메소드는 오버라이드 하지 않을 경우 템플릿의 정의를 그대로 따라가게 되고 서브클래스에서 오버라이드 한 경우에는 새로운 로직을 수행하게 된다.
public class CoffeeWithHook extends CaffeineBeverageWithHook {
public void brew() {
System.out.println("Dripping Coffee through filter");
}
public void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
public boolean customerWantsCondiments() {
String answer = getUserInput();
if (answer.toLowerCase().startsWith("y")) {
return true;
} else {
return false;
}
}
private String getUserInput() {
String answer = null;
System.out.print("Would you like milk and sugar with your coffee (y/n)? ");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch (IOException ioe) {
System.err.println("IO error trying to read your answer");
}
if (answer == null) {
return "no";
}
return answer;
}
}
- 서브클래스에서 직접 고객이 첨가물을 받을지 안 받을지를 구현하는 예시를 진행하도록 해보겠습니다.
- 먼저
customerWantsCondiments
Hook를 재정의하여 getUserInput을 통해 고객이 첨가물을 넣을지 말지를 결정하도록 했습니다
실행결과
할리우드 원칙
- 템플릿 메소드 패턴을 학습하면서 배울수 있는 객체지향 원칙은 할리우드 원칙 입니다.
- 할리우드 원칙의 정의는 의존성 부패를 방지할 수 있는 원칙입니다.
- 의존성 부패란 고수준 구성 요소가 저수준 구성 요소를 의존하고 해당 저수준 구성 요소가 또 다시 고수준 구성 요소를 의존하게 되는 반복적인 행동들로 인해 의존성이 복잡하게 꼬여있는 상황을 의존성 부패라고 정의합니다.
- 템플릿 메소드 패턴은 할리우드 원칙을 잘 지키고 있는 패턴입니다. 예시로 진행했던 CaffeineBeverage와 서브클래스들을 보면 CaffeinBeverage는 고수준 구성요소라 음료를 만드는 알고리즘만 제공하고 있는 변경하는 부분은 서브클래스가 재정의하여 진행하는것을 알 수 있습니다.
템플릿 메소드 패턴과 전략 패턴 비교
템플릿 메소드 패턴 | 전략 패턴 | |
정의 | 알고리즘 개요를 정의 | 알고리즘을 정의 |
알고리즘 수행 대상 | 서브 클래스 | 전략패턴이 알고리즘을 조건별로 변경하여 사용 |
사용 방식 | 상속 | 클라이언트게 객체 구성으로 알고리즘 구현 여부 선택권을 부여함 |
적절한 사용 시기 | 알고리즘이 유사하나 알고리즘 일부가 다른 경우 | 템플릿 메소드 패턴보다 좀 더 유연하다. |
의존성 | 전략패턴은 구성에 의존하지만 템플릿 메소드 패턴은 상속에 의존하기에 템플릿 메소드 패턴의 의존성이 더 높음 | 템플릿 메소드 패턴보다 낮음 |
정리
- 템플릿 메소드 패턴은 알고리즘 골격을 정의한다.
- 템플릿 메소드를 사용하면 알고리즘의 일부 단계를 서브클래스에서 구현할 수 있다.
- 알고리즘의 구조는 그대로 유지하면서 알고리즘의 특정 단계를 서브클래스에서 재정의할 수 도 있다.
- 템플릿 메소드 패턴은 코드 재사용에 큰 도움이된다.
- 템플릿 메소드가 들어있는 추상 클래스는 구상 메소드, 추상 메소드, 후크를 정의할 수 있다.
- 추상 메소드는 서브클래스에서 구현한다.
- 후크는 추상 클래스에 들어있는 메소드로 아무 일도 하지 않거나 기본 행동만을 정의한다.
- 서브 클래스에서 후크를 오버라이드하여 알고리즘의 흐름을 변경할 수도 있다.
- 할리우드 원칙에 따르면 저수준 모듈은 고수준 모듈에 의해 호출 시기가 결정된다.
반응형
'디자인 패턴' 카테고리의 다른 글
CHAPTER 10.상태 패턴 (0) | 2023.05.06 |
---|---|
CHAPTER 09.반복자 패턴과 컴포지트 패턴 (0) | 2023.05.06 |
CHAPTER 07.어댑터 패턴과 퍼사드 패턴 (2) | 2023.05.06 |
CHAPTER 06.커멘드 패턴 (0) | 2023.05.06 |
CHAPTER 05.싱글톤 패턴 (0) | 2023.05.06 |