예제 : 커피와 홍차 클래스

1. Coffee와 Tea 클래스

public class Coffee {
	void prepareRecipe() {
		boilWater(); // 물 끓이기
		brewCoffeeGrinds(); // 원두 필터로 내리기
		pourInCup(); // 컵에 따르기
		addSugarAndMikl(); // 설탕과 우유 추가 
	}
}

public class Tea {
	void prepareRecipe() {
		boilWater(); // 물 끓이기
		steepTeaBag(); // 티백 우리기
		pourInCup(); // 컵에 따르기
		addLemon(); // 레몬 추가
	}
}

2. 추상화

(1) Coffee와 Tea 클래스 추상화

image.png

(2) prepareRecipe() 메소드 추상화

  • CaffeineBeverage 추상 클래스
  • prepareRecipe 메소드
    • Tea와 Coffee 만들 때 동일한 prepareRecipe 메소드 사용
    • 서브 클래스에서 해당 메소드를 오버라이드 하여 제멋대로 레시피 수정 할 수 없도록 final로 선언함
  • brew 메소드와 addCondiments 메소드
    • 우리기와 첨가물 추가는 Tea와 Coffe에서 서로 다른 방식으로 처리 하므로 추상메서드로 선언
    • 두 메서드는 서브 클래스에서 알아서 하도록 함
  • Tea와 Coffee 클래스
    • CaffeineBeverage를 확장함 ( extends CaffeineBeverage )
    • 우리기(brew()) 와 첨가물 추가(addCondiments())를 override하여 정의함
// 추상 클래스 
public abstract class CaffeineBeverage {
	// final로 선언
	final void preapareRecipe() {
		boilWater();
		brea();
		pourInCup();
		addCondiments();
	}
	
	// 추상메서드 선언
	abstract void brew();
	abstract void addCondiments();
	
	void boilWater() { System.out.println("물 끓이는 중"); }
	void pourInCup() { System.out.println("컵에 따르는 중"); }
}
public class Tea extends CaffeineBeverage {
	public void brew() { System.out.println("티백 우리는 중"); }
	public void addCondiments() { System.out.println("레몬 추가 중"); }
}

public class Coffee extends CaffeineBeverage {
	public void brew() { System.out.println("원두 필터로 내리기"); }
	public void addCondiments() { System.out.println("설탕과 우유 추가 중"); }
}

템플릿 메소드 패턴

1. 템플릿 메소드 패턴 정의

템플릿 메소드 패턴

  • 메소드에서 **알고리즘의 골격(템플릿)**을 정의하기 위한 것

  • 알고리즘의 여러 단계 중 일부는 서브클래스에서 구현할 수 있음

  • 즉, 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의하고 싶을 때 사용

2. 템플릿 메소드 패턴 구조

  • AbstractClass: 추상클래스로 선언되어 실제 연산을 구현해주는 서브클래스를 만들어 사용해야 함
  • templateMethod (템플릿 메소드)
    • 서브 클래스에서 알고리즘의 각 단계를 함부로 건들 수 없도록 final로 선언함
    • 템플릿 메소드에서 각 단계들을 순서대로 정의하는데, 각 단계는 메소드로 표현됨
  • primitiveOperation (기본단계 메소드) : abstract를 선언했으므로 구상 서브 클래스에서 구현해야 함
  • concreteOperation (구상단계 메소드)
    • 구상 단계는 추상 클래스 내에서 정의됨
    • final로 정의 되었기 때문에 서브 클래스에서 오버라이드 불가능
    • 이 메소드는 템플릿 메소드에서 호출할 수도 있고, 서브클래스에서 호출해서 사용할 수도 있음
  • hook (후크 메소드)
    • 기본적으로 아무 것도 하지 않는 구상메소드를 정의할 수 있음
    • 이런 메소드를 후크(hook) 메소드라고 부름
    • 서브 클래스에서 오버라이드 할 수도 있지만, 반드시 그래야 하는 것은 아님
abstract class AbstractClass {
	// 템필릿 메소드
	final void tempalteMethod() {
			primitiveOperation1();
			primitiveOperation2();
			concreteOperation();
			hool();
	}
	
	// 기본 단계 메소드
	abstract void primitiveOperation1();
	abstract void primitiveOperation2();
	
	// 구상 단계 메소드
	final void concreteOperation() {
			// concreteOperation 메소드 실제 구현
	}
	
	void hook() {}
}

3. 템플릿 메소드와 후크

(1) 후크(hook)

후크란?

  • 추상클래스에서 선언되는 메소드긴 하지만 기본적으로 내용만 구현되어 있거나, 아무 코드도 들어있지 않은 메소드

  • 후크를 활용하면 서브 클래스 입장에서는 다양한 위치에서 알고리즘에 끼어들 수 있음

  • 후크를 사용하려면 서브 클래스에서 오버라이드 해야 함

  • 후크 용도
    • 알고리즘에서 필수적이지 않은 부분을 필요에 따라 서브 클래스에서 구현하든 말든 하도록 해야 하는 경우 후크 사용
      • 서브 클래스에서 특정 단계의 알고리즘을 수행해야 하는 경우에는 추상 메소드를 사용함
      • AbstractTemplate(추상템플릿)에 정의된 추상메소드는 ConcreteClass(구상 클래스)에서 반드시 정의해야 함 → TemplateMethod에서 요구하는 모든 단계들을 제공해야 함
      • 하지만, 알고리즘의 특정 부분이 선택적으로 적용되어야 하는 경우 후크 사용함
    • 템플릿 메소드에서 앞으로 일어날 일에 대해 서브 클래스에서 반응할 기회를 제공하기 위한 용도로 후크 사용
      • ex) 내부적으로 어떤 목록을 재정렬한 후에 서브 클래스에서 어떤 작업을 수행해야 하는 경우
      • justReOrderedList() 후크 메소드 만들어서 사용
    • 서브 클래스에 추상클래스에서 진행되는 작업에 대한 결정을 내리는 기능을 부여하기 위한 용도로 후크 사용
      • ex) 손님이 첨가물 추가여부에 따라 특정 메소드 호출해야 하는 경우
      • customerWantsCondiments() 후크 메소드 만들어서 사용
  • 추상 메소드와 후크 메소드
    • 추상메소드가 너무 많으면 서브 클래스에서 일일이 구현하기 불편함
    • 알고리즘을 너무 잘게 쪼개지 않는 것도 하나의 방법이지만, 큼직하게 추상 메소드를 구현시 유연성이 떨어짐
    • 필수적이지 않은 부분은 추상클래스가 아닌 후크로 구현하면, 해당 추상 클래스의 서브 클래스 만들 때 부담이 적어짐

(2) 후크 활용 코드 예시

  • customerWantsCondiments()
    • 구상 메서드이며, 내부 로직은 true만 리턴할 뿐 별 내용 없는 기본 메소드를 구현함
    • 이 메소드는 서브 클래스에서 필요에 따라 오버라이드 할 수 있는 메소드이므로 hook임
  • 조건문
    • customerWantsCodiments() 메서드에 의해 실행여부가 결정되는 조건문 추가됨
    • 손님이 첨가물 넣어달라고 했을 때만, addCondiments() 호출
public abstract class CaffeineBeverageWithHook {
		void prepareReceipe() {
			boilWater();
			brew();
			pourInCup();
			if(customerWantsCodiments()) {
					addCondiments();
			}
		}
		
		abstract void brew();
		abstract void addCondiments();
		
		void boilWater() { System.out.println("물 끓이는 중"); }
		void pourInCup() { System.out.println("컵에 따르는 중"); }
		
		boolean customerWantsCondiments() { return true; }	
}
  • 후크 활용
    • 후크를 사용하려면 서브 클래스에서 오버라이드 해야 함
    • 예시에서는 음료에 첨가물을 추가할지 처리 여부를 결정하기 위한 용도로 후크를 사용함
public class Coffee extends CaffeineBeverageWithHook {
	public void brew() { System.out.println("원두 필터로 내리기"); }
	public void addCondiments() { System.out.println("설탕과 우유 추가 중"); }
	
	// 후크를 오버라이드해서 원하는 기능을 집어 넣음
	// 기능 : 손님의 첨가물 추가 여부 답변에 따라 true 또는  false를 리턴함
	public boolean customerWantsCondiments() {
			String answer = getUserInput(); 
			if (answer.equals("yes")) { reuturn true; }
			return false;
	}
}