객체지향 및 기반 언어/디자인 패턴

[Design Patterns] Chapter 08 템플릿 메소드 패턴

박지환 2022. 10. 9. 15:04

템플릿 메소드 패턴의 정의

템플릿 메소드 패턴(Template Method Pattern)은 알고리즘의 골격을 정의한다. 템플릿 메소드를 사용하면 알고리즘의 일부 단계를 서브클래스에서 구현할 수 있으며, 알고리즘의 구조는 그대로 유지하면서 알고리즘의 특정 단계를 서브클래스에서 재정의할 수도 있다.

템플릿 메소드 패턴 UML

추상 클래스에 선언된 템플릿 메소드는 일련의 알고리즘을 정의하며, 일부 알고리즘은 abstract 메소드로 선언되어 있다. abstract로 선언되었던 단계들은 구상 클래스에서 구현한다.

템플릿 메소드 예제 코드

public abstract class CaffeineBeverage {

    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if(customerWantsCondiments()) {
            addCondiments();
        }
    }

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

    void boilWater() {
        System.out.println("물 끓이는 중");
    }

    void pourInCup() {
        System.out.println("컵에 따르는 중");
    }

    boolean customerWantsCondiments() {
        return true;
    }
}

CaffeineBeverage 추상 클래스의 prepareRecipe()는 boilWater() -> brew() -> pourInCup() ->customerWantsCondiments() -> addCondiments() 이라는 카페인 음료를 끓이는 일련의 메소드 (알고리즘)을 정의하고 있다. 

또한 prepareRecipe()를 final로 선언하여, 서브 클래스들이 카페인 음료 제조법의 과정 (알고리즘)을 바꿀 수 없다.

public class Coffee extends CaffeineBeverage {

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

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

    @Override
    boolean customerWantsCondiments() {
        String answer = getUserInput();

        if (answer.toLowerCase().startsWith("y")) {
            return true;
        } else {
            return false;
        }
    }

    private String getUserInput() {
        String answer = null;

        System.out.println("커피에 우유와 설탕을 넣을까요? (y/n)? ");

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        try {
            answer = in.readLine();
        } catch (IOException ioe) {
            System.err.println("IO 오류");
        }
        if(answer == null) {
            return "no";
        }
        return answer;
    }
}

Coffee 클래스는 CaffeineBeverage 클래스를 구현하며, brew(), addCondiments(), customerWantsCondiments() 메소드를 오버라이드하여 카페인 음료 제조의 일부 과정을 커피만의 제조 과정으로 바꾸었다.

public class Tea extends CaffeineBeverage {

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

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

Tea 클래스는 또한 CaffeineBeverage 클래스를 구현하며, brew(), addCondiments() 메소드를 오버라이드하여 카페인 음료 제조의 일부 과정을 차만의 제조 과정으로 바꾸었다.

public class BeverageTestDrive {

    public static void main(String[] args) {
        Tea tea = new Tea();
        Coffee coffee = new Coffee();

        System.out.println("\n홍차 준비 중...");
        tea.prepareRecipe();

        System.out.println("\n커피 준비 중...");
        coffee.prepareRecipe();
    }
}

Tea와 Coffee의 prepareRecipe() 메소드를 호출하면, CaffeineBeverage 추상 클래스의 prepareRecipe()는 boilWater() -> brew() -> pourInCup() ->customerWantsCondiments() -> addCondiments() 이라는 카페인 음료를 끓이는 일련의 메소드 (알고리즘)은 유지되지만, brew(), addCondiments() 등의 알고리즘의 일부 단계들은 Coffee와 Tea만의 알고리즘으로 구성되어 실행되게 된다.

템플릿 메소드 속 훅(Hook) 알아보기

(Hook)은 추상 클래스에서 선언되지만 기본적인 내용만 구현되어 있거나, 아무 코드도 들어있지 않은 메소드로, 서브 클래스에서 오버라이드하여 템플릿 메소드의 알고리즘 흐름에 끼어들 수 있다.

if(customerWantsCondiments()) {
    addCondiments();
}
@Override
boolean customerWantsCondiments() {
    String answer = getUserInput();

    if (answer.toLowerCase().startsWith("y")) {
        return true;
    } else {
        return false;
    }
}

위 예제의 customerWantsCondiments()가 훅(Hook)에 해당되며, 이 메소드를 Coffee 클래스가 오버라이드하여 input에 따라 addCondiments()가 수행되거나, 수행되지 않게 구성하고 있다.

할리우드 원칙

디자인 원칙 8: 할리우드 원칙 고수준 구성 요소에서 모든 것을 관리하고, 필요한 저수준 구성 요소를 불러서 써야 함. 이를 통해 의존성 부패(dependency rot, 의존성이 복잡하게 꼬인 상황)을 방지할 수 있음

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

템플릿 메소드를 가진 추상 클래스(고수준 구성 요소)와, 템플릿 메소드에 추상화된 부분은 구현하는 서브클래스(저수준 구성 요소)는 할리우드 원칙을 활용하고 있다.

자바 API 속 템플릿 메소드 패턴 알아보기

템플릿 메소드 패턴은 프레임워크를 만드는 데 아주 훌륭한 디자인 도구임. (프레임워크로 작업이 처리되지는 방식을 제어하면서도 프레임워크에서 처리하는 알고리즘의 각 단계를 사용자가 마음대로 지정할 수 있기 때문)

템플릿 메소드를 사용하는 자바 API

  • Arrays 클래스 정렬: Comparable 인터페이스의 compareTo() 메소드를 구현하는 객체를 배열에 넣고 정렬할 수 있음.
  • JFrame의 paint() 후크 메소드를 구현하여 특정 화면 영역에 특정 내용을 표시할 수 있음.
  • AbstractList 추상 클래스의 get()과 size() 추상 메소드를 구현하여 나만의 커스텀 리스트를 구현할 수 있음.

핵심 정리

  • 템플릿 메소드는 알고리즘의 단계를 정의하며 일부 단계를 서브클래스에서 구현하도록 할 수 있음.
  • 템플릿 메소드 패턴은 코드 재사용에 큰 도움이 됨.
  • 템플릿 메소드가 들어있는 추상 클래스는 구상 메소드, 추상 메소드, 후크를 정의할 수 있음.
  • 추상 메소드는 서브클래스에서 구현함.
  • 후크는 추상 클래스에 들어있는 메소드로 아무 일도 하지 않거나 기본 행동만을 정의함. 서브 클래스에서 후크를 오버라이드 할 수 있음.
  • 할리우드 원칙에 의하면, 저수준 모듈을 언제 어떻게 호출할지는 고수준 모듈에서 결정하는 것이 좋음.
  • 템플릿 메소드 패턴은 실전에서도 꽤 자주 쓰이지만 반드시 “교과서적인” 방법으로 적용되진 않음.
  • 전략 패턴과 템플릿 메소드 패턴은 모두 알고리즘을 캡슐화하는 패턴이지만 전략 패턴은 구성을, 템플릿 메소드 패턴은 상속을 사용함.
  • 팩토리 메소드 패턴은 특화된 템플릿 메소드 패턴임.

 

GitHub Code Reference: https://github.com/Krapi0314/Design_Patterns

 

GitHub - Krapi0314/Design_Patterns: Java Implementation of GoF's Design Patterns

Java Implementation of GoF's Design Patterns. Contribute to Krapi0314/Design_Patterns development by creating an account on GitHub.

github.com

Reference: http://www.yes24.com/Product/Goods/108192370

 

헤드 퍼스트 디자인 패턴 - YES24

유지관리가 편리한 객체지향 소프트웨어 만들기!“『헤드 퍼스트 디자인 패턴(개정판)』 한 권이면 충분하다.이유 1. 흥미로운 이야기와 재치 넘치는 구성이 담긴 〈헤드 퍼스트〉 시리즈! 하나

www.yes24.com