커피와 홍차 만들기
커피 메뉴얼
물을 끓인다.
끓는 물에 커피를 우려낸다
커피를 컵에 따른다
설탕과 우유를 추가한다
물을 끓인다
끓는 물에 찻잎을 우려낸다
홍차를 컵에 따른다
레몬을 추가한다
다음과 같은 메뉴얼이 있다.
각 메뉴얼 토대로 class를 만들어 볼 예정이다
커피
public class Coffee {
void prepareRecipe(){
boilWater();
brewCoffeeGrinds();
pourIncup();
addSugarAndMilk();
}
private void boilWater() {
System.out.println("물긇이는 중");
}
private void brewCoffeeGrinds() {
System.out.println("필터로 커피 우려내는 중");
}
private void pourIncup() {
System.out.println("컵에 따르는 중");
}
private void addSugarAndMilk() {
System.out.println("설탕과 우유를 추가하는 중 ");
}
}
홍차
public class Tea {
void prepareRecipe(){
boilWater();
steepTeaBag();
pourIncup();
addLemon();
}
private void boilWater() {
System.out.println("물긇이는 중");
}
private void steepTeaBag() {
System.out.println("찻잎 우려는중");
} // 홍차 정용 메서드
private void addLemon() {
System.out.println("레몬을 추가하는 중 ");
} // 홍차 전용메서드
private void pourIncup() {
System.out.println("컵에 따르는 중");
}
}
또한 생각을 해보면 커피를 필터로 우려내는 일과 티백을 물에 넣어 홍차를 만드는 것 별반 다르지 않다 .
brew() 메소드를 만들어 커피를 우려내든 홍차를 우려내든 같은 메소드를 쓸것이다.
또한 설탕과 우유를 추가하는 일이나 레몬을 추가한다 또한 별반 다름이 없다
이 또한 addCondiments() 메소드를 사용할 것이다
public abstract class CaffeineBeverage {
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
addCondiments();
}
abstract void brew();
abstract void addCondiments();
private void boilWater() {
System.out.println("물끓이는중 ");
}
private void pourInCup() {
System.out.println("컵에 따르는 중 ");
}
}
그럼 다음같이
public class ATea extends CaffeineBeverage{
@Override
void brew() {
System.out.println("찻잎 우리는 중");
}
@Override
void addCondiments() {
System.out.println("레몬을 추가하는 중");
}
}
public class ACoffee extends CaffeineBeverage{
@Override
void brew() {
System.out.println("필터로 커피를 우려내는 중");
}
@Override
void addCondiments() {
System.out.println("설탕과 우유를 추가하는 중");
}
}
단순히 coffee 와 tea 클래스를 사용했을 때에 비교하여,
템플릿 메소드가 있는 CaffeineBeverage를 사용하면 다음과 같은 장점을 가진다.
CaffeubeBeverage 클래스에서 작업을 처리한다 (알고리즘 독점한다)
CaffeineBeverage 덕분에 서브클래스에서 코드를 재사용할 수 있다.
알고리즘이 한군데 모여있으므로 한 부분만 수정하면 된다
다른 음료도 쉽게 추가할수 있는 프레임워크를 제공한다
CaffeineVeverage 클래스에 알고리즘 지식이 집중되어 있으며 일부 구현만 서브클래스 의존한다
후크 : 추상클래스에서 선언되는 메소드지만 기본적인 내용만 구현 or 아무 코드도 들어있지 않는 메소드 서브 클래스 입장에선 다양한 위치에서 알고리즘에 끼어들수 있음
public abstract class CaffeineBeverageWithHook {
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
if(customerWantsCondiments()){
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
private void boilWater() {
System.out.println("물끓이는중 ");
}
private void pourInCup() {
System.out.println("컵에 따르는 중 ");
}
boolean customerWantsCondiments(){ // 후크
return true;
}
}
다음 코드에서 템플릿메소드는 어디이며 후크는 어느 부분일까
그럼 후크는 어디일까
바로 customerWantwsCondiments() 일 것이다.
후크를 사용하면 서브 클래스가 다양한 위치에서 알고리즘을 끼어들 수 있다.
이건 무슨 소리일까
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
if(customerWantsCondiments()){ // 여기에서 사용해요
addCondiments();
}
}
다음과 같이 prepareRecipe() 에서 addCondiments()의 실행 여부를 구하기 위해 사용한다
그럼 서브클래스에서
public class CoffeeWithHook extends CaffeineBeverageWithHook{
@Override
void brew() {
System.out.println("필터로 커피를 우려내는 중");
}
@Override
void addCondiments() {
System.out.println("설탕과 우유를 추가하는 중");
}
public boolean customerWantsCondiments(){ // 고객의 입력에 따라 true false 리턴
String answer = getUserInput();
if(answer.toLowerCase().startsWith("y")){
return true;
}else{
return false;
}
}
private String getUserInput() {
String answer = null;
System.out.println("커피에 우유와 설탕을 넣을까요?");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try{
answer = in.readLine();
} catch (IOException e) {
System.err.println("IO 오류");
}
if(answer == null){
return "no";
}
return answer;
}
}
다음과 같이 고객의 입력에 따라 true false 를 리턴하도록 하여 addCondiments() 실행 여부를 서브클래스에서 결정할 수 있게 되었다.
디자인 원칙
먼저 연락하지 마세요 저희가 연락드리겠습니다.
할리우드 원칙을 사용시 의존성 부패를 방지할 수 있다.
여기서 의존성 부패는 의존성이 복잡하게 꼬여있는 상황이다
어떤 고수준 구성 요소가 저수준 구성 요소에 의존하고 그 저수준 구성요소는 다시 고수준 구성 요소에 의존하고 또 그 고수준 구성 요소는 다시 또 다른 구성 요소에 의존하고 이러한 의존성이 복잡하게 꼬여있는 상항
즉 먼저 연락하지 마세요 저희가 연락드리겠습니다 이말은 고수준 구성요소가 저수준 구성요소에게 하는 말
그럼 이것이 왜 템플릿 메소드 패턴에 나오는냐
우선 CoffeineBaverage는 고수준 구성요소이다. 음료 만드는 방법에 해당하는 알고리즘을 장악하고 있다 메소드 구현이 필요한 상황에만 서브 클래스를 불러낸다.
그럼 Coffee , Tea는 서브클래스이다 CaffeineBaverage 클래스의 클라이언트는 Tea나 Coffee 가 아닌 CaffeineBaverage에 추상되어 있는 부분에 의존
Tea나 Coffee는 호출 당하기 전까지 추상 클래스를 직접 호출하지 않는다
템플릿 메소드 패턴 정리
알고리즘의 골격을 정의한다
템플릿 메소드를 사용시 알고리즘의 일부 단계를 서브클래스에서 구현(할리우드 원칙) 할 수 있으며, 알고리즘 구조 유지하며 알고리즘 특정 단계를 서브 클래스에서 재정의할 수 있다.