자판기 사업을 새로 시작한 A씨는 커피와 레몬차를 판매하기로 결정하고 이를 제어할 자판기 제어 프로그램을 개발하도록 시스템 개발자에게 요구했고 아래와 같이 시스템이 개발되었다.
Coffee & Tea 조리 메뉴얼
Coffee 조리법
물을 끓인다.
끊인 물에 커피를 넣는다.
컵에 커피를 붓는다.
설탕 및 우유를 추가한다.
Tea 조리법
물을 끓인다.
끓인 물에 Tea를 우려낸다.
컵에 Tea를 붓는다.
레몬을 추가한다.
package templateMethod;
public class Client {
public static void main(String[] args) {
Client client = new Client();
Coffee coffee = client.pressCoffeeButton();
coffee.prepareRecipe();
System.out.println();
Tea tea = client.pressTeaButton();
tea.prepareRecipe();
}
public Coffee pressCoffeeButton() {
System.out.println("커피 버튼을 누르셨습니다.");
return new Coffee();
}
public Tea pressTeaButton() {
System.out.println("레몬차 버튼을 누르셨습니다.");
return new Tea();
}
}
package templateMethod;
public class Coffee {
public void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void boilWater() {
System.out.println("Bolling Water...");
}
public void brewCoffeeGrinds() {
System.out.println("Dripping Coffee through filter...");
}
public void pourInCup() {
System.out.println("Pouring into cup...");
}
public void addSugarAndMilk() {
System.out.println("Adding Sugar and Milk...");
}
}
package templateMethod;
public class Tea {
public void prepareRecipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
public void boilWater() {
System.out.println("Bolling Water...");
}
public void steepTeaBag() {
System.out.println("Steeping the Tea...");
}
public void pourInCup() {
System.out.println("Adding Lemon...");
}
public void addLemon() {
System.out.println("Adding Lemon...");
}
}
커피 버튼을 누르셨습니다.
Bolling Water...
Dripping Coffee through filter...
Pouring into cup...
Adding Sugar and Milk...
레몬차 버튼을 누르셨습니다.
Bolling Water...
Steeping the Tea...
Pouring into cup...
Adding Lemon...
솔루션 찾기 (문제해결 실습)
▶ 추상화 또는 일반화 하기
커피와 레몬차를 조리하는 조리법을 기준으로 추상화 할 수 있는 부분을 찾아 이들의 관계를 다이어그램에 표현하시오.
표현된 다이어그램을 기준으로 구현하고 제대로 동작하는지 여부를 확인하시오.
▶ 알고리즘 캡슐화 하기
커피와 레몬차 조리법의 알고리즘의 단계를 정의하고 하위 클래스에서 하나 또는 그 이상의 단계를 구현하도록 한다.
알고리즘 캡슐화 하기 이전과 이후를 비교해 보아라.
솔루션 발표
문제해결을 위해 디자인한 다이어그램과 개발 소스를 기준으로 발표
패턴소개 - Template Method 패턴
의도
- 오퍼레이션에 알고리즘의 기본 골격 구조를 정의
-구체적인 단계는 하위클래스에 정의
-Template Method 클래스의 하위클래스는 알고리즘의 구조를 변경하지 않고 알고리즘의 처리 단계를 재정의
동기
Problem을 통해서 나타난 문제점을 Template Method 패턴을 통해 그 대안을 찾고자 한다.
-추상화 또는 일반화 하기
-알고리즘 캡슐화 하기 (Encapsulating Algorithms)
추상화 또는 일반화 하기
커피와 레몬차 조리법을 보면 공통된 행위를 찾을 수 있음.
즉, 물을 끓이거나 컵에 담는 행위는 동일하고 이를 추상클래스의 행위로 추출
package templateMethod;
public abstract class Bevarage {
//boilWater(), pourInCup()메소드는
//하위클래스에서 공통으로 사용되고 있으므로
//상위클래스에서 정의
public void boilWater() {
System.out.println("Bolling Water...");
}
public void pourInCup() {
System.out.println("Pouring into cup...");
}
//prepareRecipe()메소드는 각각의 하위클래스마다 다르게 정의된다.
//이를 추상메소드로 정의.
public abstract void prepareRecipe();
}
package templateMethod;
public class Coffee extends Bevarage {
public void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void brewCoffeeGrinds() {
System.out.println("Dripping Coffee through filter...");
}
public void addSugarAndMilk() {
System.out.println("Adding Sugar and Milk...");
}
}
package templateMethod;
public class Tea extends Bevarage {
public void prepareRecipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
public void steepTeaBag() {
System.out.println("Steeping the Tea...");
}
public void addLemon() {
System.out.println("Adding Lemon...");
}
}
prepareRecipe() 추상화 하기
커피와 레몬차의 조리법의 알고리즘을 살펴보면 동일한 흐름을 가지고 있음.
이를 통해서 prepareRecipe()를 추상화 방안 고려
커피와 레몬차 조리법 알고리즘 - prepareRecipe
1. 물을 끓인다.
2. 커피 또는 차를 추출하여 뜨거운 물에 넣는다.
3. 음료조리 결과를 컵에 담는다.
4. 기호에 맞게 음료에 첨가물을 넣는다.
1,3 : 이미 추상클래스 내에 정의되어있다.
2,4 : 추상클래스에 정의되지 않았지만 알고리즘은 동일하다. 단지 음료마다의 차이만 있을 뿐
알고리즘 캡슐화 하기 (Encapsulating Algorithms)
2,4의 행위를 좀 더 일반화 시켜야함
package templateMethod;
public abstract class Bevarage {
public void boilWater() {
System.out.println("Bolling Water...");
}
public void pourInCup() {
System.out.println("Pouring into cup...");
}
//brew()와 addCondiments() 메소드는 커피와 레몬차가 서로 다른 조리법을 적용하므로
//추상메소드로 선언하고 하위클래스에서 이를 구현한다.
public abstract void brew();
public abstract void addCondiments();
//이제 커피나 레몬차나 동일한 조리법 메소드를 사용할 수 있다.
//prepareRecipe()메소드는 이제 하위클래스에서 재구현 할 수 없게 final로 선언
public final void prepareRecipe(){
boilWater();
brew();
pourInCup();
addCondiments();
}
}
package templateMethod;
public class Coffee extends Bevarage {
@Override
public void brew() {
System.out.println("Dripping Coffee through filter...");
}
@Override
public void addCondiments() {
System.out.println("Adding Sugar and Milk...");
}
}
package templateMethod;
public class Tea extends Bevarage {
@Override
public void brew() {
System.out.println("Steeping the Tea...");
}
@Override
public void addCondiments() {
System.out.println("Adding Lemon...");
}
}
Templete 메소드와 Hook 메소드
▶ 템플릿 메소드는 알고리즘 단계를 정의하고 하나 또는 그 이상의 단계를 하위클래스에서 구현하도록 제공.
▶ 훅 메소드는 추상클래스에 정의된 메소드들 중에서 구현이 비어있거나 기본 구현만 되어 있는 메소드를 의미.
prepareRecipe() 메소드는 템플릿 메소드이다. 그 이유는 전체를 수행하는 메소드이고 또한 알고리즘에 대한 템플릿을 제공하기 때문이다.
템플릿에서 알고리즘의 각 단계는 메소드에 의해서 표현된다.
훅 메소드는 하위클래스에서 알고리즘의 변화를 주기 위해서 구현할 수도 있고, 그렇지 않다면 훅 메소드를 무시할 수 있다.
customerWantsCondiments() 메소드는 하위클래스에서 알고리즘 변화가 필요할 경우 구현할 수 있다. 원하지 않을 경우에는 기본적인 구현이 있으므로 알고리즘 수행에 영향이 없다.
package templateMethod;
public abstract class Bevarage {
public void boilWater() {
System.out.println("Bolling Water...");
}
public void pourInCup() {
System.out.println("Pouring into cup...");
}
public abstract void brew();
public abstract void addCondiments();
public final void prepareRecipe(){
boilWater();
brew();
pourInCup();
//템플릿 메소드 내에 훅 메소드 적용
if(customerWantsCondiments()){
addCondiments();
}
}
//customerWantsConditions() 메소드는
//하위클래스에서 알고리즘 변화가 필요할 경우 구현할 수 있다.
//원하지 않을 경우에는 기본적인 구현이 있으므로 알고리즘 수행에 영향이 없다.
public boolean customerWantsCondiments() {
return true;
}
}
할리우드 원칙 (Hollywood Principle)
의존성 문제로 고수준의 구성요소가 저수준의 구성요소에 의존하고 반대로 저수준의 구성요소가 고수준의 구성요소에 의존하게 되면 의존성이 꼬이게 됨.
Template Method 패턴에서도 템플릿메소드를 포함하고 있는 상위클래스가 전체 알고리즘을 제어하고 하위클래스는 알고리즘의 각 단계를 구현하고 있어 할리우드 원칙을 적용한 좋은 예이다.
High-Level Component는 알고리즘 시작과 방법을 제어한다.
Low-Level Component는 각각 자신의 알고리즘에 충실하면 된다.
Low-Level Component는 절대 직접 High-Level Component를 호출할 수 없다.
package templateMethod;
public abstract class Bevarage {
public void boilWater() {
System.out.println("Bolling Water...");
}
public void pourInCup() {
System.out.println("Pouring into cup...");
}
public abstract void brew();
public abstract void addCondiments();
public final void prepareRecipe(){
boilWater();
brew();
pourInCup();
//템플릿 메소드 내에 훅 메소드 적용
if(customerWantsCondiments()){
addCondiments();
}
}
//customerWantsConditions() 메소드는
//하위클래스에서 알고리즘 변화가 필요할 경우 구현할 수 있다.
//원하지 않을 경우에는 기본적인 구현이 있으므로 알고리즘 수행에 영향이 없다.
public boolean customerWantsCondiments() {
return true;
}
}
package templateMethod;
public class Coffee extends Bevarage {
boolean customerWantsCondiments = true;
@Override
public void brew() {
System.out.println("Dripping Coffee through filter...");
}
@Override
public void addCondiments() {
System.out.println("Adding Sugar and Milk...");
}
public void setCustomerWantsCondiments(boolean customerWantsCondiments) {
this.customerWantsCondiments = customerWantsCondiments;
}
@Override
public boolean customerWantsCondiments() {
return customerWantsCondiments;
}
}
package templateMethod;
public class Tea extends Bevarage {
@Override
public void brew() {
System.out.println("Steeping the Tea...");
}
@Override
public void addCondiments() {
System.out.println("Adding Lemon...");
}
}
package templateMethod;
public class Client {
public static void main(String[] args) {
Client client = new Client();
Coffee coffee = client.pressCoffeeButton();
coffee.prepareRecipe();
System.out.println();
Tea tea = client.pressTeaButton();
tea.prepareRecipe();
System.out.println();
System.out.println("블랙커피버튼을 누르셨습니다.");
((Coffee) coffee).setCustomerWantsCondiments(false);
coffee.prepareRecipe();
}
private Coffee pressCoffeeButton() {
System.out.println("커피 버튼을 누르셨습니다.");
return new Coffee();
}
private Tea pressTeaButton() {
System.out.println("레몬차 버튼을 누르셨습니다.");
return new Tea();
}
}
커피 버튼을 누르셨습니다.
Bolling Water...
Dripping Coffee through filter...
Pouring into cup...
Adding Sugar and Milk...
레몬차 버튼을 누르셨습니다.
Bolling Water...
Steeping the Tea...
Pouring into cup...
Adding Lemon...
블랙커피버튼을 누르셨습니다.
Bolling Water...
Dripping Coffee through filter...
Pouring into cup...
적용
▶ 알고리즘의 변하지 않는 부분을 한 번 정의하고 다양해질 수 있는 부분을 하위클래스에서 정의할 수 있도록 구현하고자 할 때
▶ 하위클래스 사이의 공통적인 행위를 추출하여 하나의 공통 클래스로 정의할 때
- 일반화를 위한 리펙토링의 예라고 볼 수 있음
-기존 코드에서의 차이를 식별한 후 이를 새로운 오퍼레이션으로 구현할 수 있음
-차이를 보이는 부분을 템플릿메소드로 정의하고 나중에 상속받은 하위클래스가 정의하는 오퍼레이션을 호출하게 함