본문 바로가기

기타/디자인패턴

[Design Pattern] Template Method Pattern

해당 포스팅은 한빛 미디어 헤드퍼스트 디자인 패턴(에릭 프리먼, 엘리자베스 롭슨 저)를 통해 공부한 내용을 정리한 블로그입니다.

아직 많이 부족하고 배울게 너무나도 많습니다. 틀린내용이 있으면 언제나 가감없이 말씀해주시면 감사하겠습니다😁
좋은 프로그래머는 자기 두뇌를 사용한다. 그러나 좋은 가이드라인은 모든 케이스를 고려해야만 하는 노력을 줄여준다.
(Francis Glassborow, 개발자)

Coffee 클래스와 Tea 클래스 만들기


  • 커피와 홍차 만드는 방법은 비슷.
  • 비슷한 클래스는 공통된 부분을 추상화해서 베이스 클래스로 만들면 좋은 방법
  • 추상화 클래스 적용.

  • Coffee 클래스에서도 Tea 클래스에서도 추상화 가능한 메소드를 추가
    • brewCoffeeGrinds(), steepTeaBag() 의 역할이 비슷
    • addSugarAndMilk(), addLemon()의 역할이 비슷
    • 각각 brew()addCondiments()로 추상화
  • 일반화를 최대한 적용. ⇒ CaffeinBeverage 클래스에 반영
  • 일부 메서드를 서브 클래스에 의존 ⇒ 각각의 brew()addCondiments()를 추상화

Template Method Pattern


  • prepareRecipe()는 템플릿 메소드
    • 메소드이자 카페인 음료수를 만드는 알고리즘의 템플릿
  • 이전 코드
    • 음료 코드 각자가 알고리즘 수행.
    • 중복, 분산 코드가 많음.
    • 알고리즘이 바뀌면 수정해야 할 부분이 여러군데임.
  • 템플릿 메소드 적용 코드
    • 알고리즘을 CaffeineBeverage가 독점
    • 서브 클래스에서 코드를 재사용 가능.
    • 다른 음료도 쉽게 추가할 수 있는 알고리즘 제공. => 알고리즘의 템플릿 만들기
💡 템플릿 메소드
알고리즘의 골격을 정의. 템플릿 메소드를 사용하면 알고리즘의 일부 단계를 서브 클래스에서 구현할 수 있으며, 알고리즘의 구조는 그대로 유지하면서 알고리즘의 특정 단계를 서브 클래스에서 재정의.

Example

public abstract class CaffeineBeverage {
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    abstract void brew();
    abstract void addCondiments();

    public void pourInCup() {
        System.out.println("컵에 따르는 중");
    }
    public void boilWater() {
        System.out.println("물 끓이는 중");
    }
}

public class Coffee extends CaffeineBeverage{

    @Override
    public void brew() {
        System.out.println("필터로 커피 우려내는 중");
    }

    @Override
    public void addCondiments() {
        System.out.println("설탕과 우유를 추가하는 중");
    }
}

public class Tea extends CaffeineBeverage{

    @Override
    public void brew() {
        System.out.println("찻잎을 우려내는 중");
    }

    @Override
    public void addCondiments() {
        System.out.println("레몬을 추가하는 중");
    }
}

hook

  • 기본적으로 아무것도 하지 않는 구상 메소드를 정의.
  • 서브 클래스에서 오버라이드할 수도 있고 아닐 수도 있음.
    • 서브 클래스에서 메소드를 커스텀해서 사용할 수 있음.
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;
	}
}

고찰

  • 제공해야만 하는 기능 ⇒ 추상 메소드
  • 서브 클래스 전용 커스텀후크
  • 추상 메소드가 너무 많아지는 문제.
    • 템플릿 메소드를 만들 때에 이 점을 염두.
    • 모든 단계가 필수가 아니므로 후크 사용

할리우드 원칙


먼저 연락 X. 연락 주겠어

  • 할리우드 원칙을 사용하면 dependency rot를 방지.
    • 의존성 부패 ⇒ 고수준 구성요소와 저수준 구성요소가 서로서로 의존하는 형태.

할리우드 원칙과 템플릿 메소드 패턴

  • CaffeineBeverage 클래스는 고수준 구성요소
  • Tea, Coffee(구상 클래스)가 고수준 구성요소를 호출하지 않음.

프레임 워크를 만드는데 적절한 패턴

  • Arrays.sort() 에서 사용되는 패턴
    • 서브 클래스를 구현하기 위해서 Comparable 인터페이스 제공을 통해 compareTo() 구현하도록 유도
  • JFrame 클래스에서 사용
    • paint() 함수가 후크