• Home
  • About
    • JUNGEEYOU photo

      JUNGEEYOU

      안녕하세요~ 유정이 입니다:)

    • Learn More
    • Email
    • LinkedIn
    • Github
  • Design Patterns
  • Algorithm
  • Python
  • MongoDB
  • Redis
  • Docker
  • Projects
  • Seminar
  • Etc

8. 템플릿 메소드 패턴(Template Method)

21 Dec 2019

Reading time ~4 minutes

👀 학습 목표

  • 알고리즘을 캡슐화해서 서브클래스를 언제든 필요할 때 쓸 수 있는 템플릿 메소드를 이해하자.
  • 템플릿 메소드 패턴을 적용해보자.

1. 템플릿 메소드 패턴이란?

템플릿 메소드 패턴 정의

메소드에서 알고리즘의 골격을 정의합니다. 알고리즘의 여러 단계 중 일부는 서브 클래스에서 구현할 수 있습니다. 템플릿 메소드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의할 수 있습니다.

8/Untitled.png

sourcemaking 이미지 참고

  • 간단히 말하면 알고리즘의 틀을 만들기 위한 것
    • 틀(템플릿)이란? 일련의 단계들로 알고리즘을 정의한 메소드

2. 템플릿 메소드 패턴 적용하기

2-1. 커피와 차 제조법

  • 커피와 차 제조법 클래스를 만들어 보자.

☕️커피 제조법

  1. 물을 끓인다.
  2. 끓는 물에 커피를 우려낸다.
  3. 커피를 컵에 따른다.
  4. 설탕과 우유를 추가한다.

🍵차 제조법

  1. 물을 끓인다.
  2. 끓는 물에 차를 우려낸다.
  3. 차를 컵에 따른다.
  4. 레몬을 추가한다.

다이어그램

8/Untitled%201.png

코드

    // 1. 커피 만드는 법 
    public class Coffee {
    		// 커피 만드는 법: 아래 메소드로 만들어 었다. 
        void prepareRecipe() {
            boilWater();
            brewCoffeeGrinds();
            pourInCup();
            addSugarAndMilk();
        }
    
        public void boilWater() {
            System.out.println("Boiling 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");
        }
    }
    
    // 2. 차 만드는 법 
    public class Tea {
    
        void prepareRecipe() {
            boilWater();
            steepTeaBag();
            pourInCup();
            addLemon();
        }
    
        public void boilWater() {
            System.out.println("Boiling water");
        }
    
        public void steepTeaBag() {
            System.out.println("Steeping the tea");
        }
    
        public void addLemon() {
            System.out.println("Adding Lemon");
        }
    
        public void pourInCup() {
            System.out.println("Pouring into cup");
        }
    }

의문

  • 그런데 서로 중복되는 부분이 많은 것 같다. 거의 클래스가 비슷하니 공통적인 부분을 추상화하여 베이스 클래스를 만드는 것이 좋지 않을까?

2-2. 추상화 방법 & 알고리즘 추상화

  • 추상화된 클래스로 만들어 보자.
  • 공통점
    • 물을 끓인다.
    • 컵에 따른다.
    • 제조 순서 동일

다이어그램

  • CaffeineBeverage: 추상 클래스로 “우리기(brew)”, “첨가물 넣기(addCondiments)” 메소드는 서브 클래스 별로 정의 내용이 달라서 추상 메소드로 구성되었고, 나머지 메소드는 동일하기에 추상 클래스에서 정의함.
    • prepareRecipe: 제조 순서 알고리즘(= 템플릿 메소드) 은 서브클래스가 동일하여 final 로 구성됨
  • Tea, Coffee 클래스: 서로 다른 부분인 추상 메소드만 각 각 구현함

8/Untitled%202.png

코드

    // 1. 추상 클래스로 카페인 음료 클래스 
    public abstract class CaffeineBeverage {
    		// final: 서브 클래스에서 마음대로 건들이는 것을 방지하기 위해 선언 
        final void prepareRecipe(){    // 템플릿 메소드로 알고리즘에 대한 틀 역할을 제공한다. 
        boilWater();
        brew();
        pourInCup();
        addCondiments();
        }
    
        abstract void brew();
        abstract void addCondiments();
    
       final void boilWater(){    // final로 선언하여 서브 클래스에서 오버라이드 못하도록 한다. 
            System.out.println("물 끓이는 중");
        }
       final void pourInCup(){
            System.out.println("컵에 따르는 중");
        }
    }
    
    // 2. 서브클래스로 티 클래스 
    public class Tea extends CaffeineBeverage {
        public void brew(){
            System.out.println("차를 우려내는 중");
        }
        public void addCondiments(){
            System.out.println("레몬을 추가하는 중");
        }
    }
    
    3. 서브클래스로 커피 클래스 
    public class Coffee extends CaffeineBeverage {
        public void brew(){
            System.out.println("필터로 커피를 우려내는 중");
        }
        public void addCondiments(){
            System.out.println("설탕과 커피를 추가하는 중 ");
        }
    }

2-3. 템플릿 메소드 정의 및 비교

템플릿 메소드와 심플 클래스 적용 내용 비교

심플 클래스(2-1 구현 내용)

  1. 커피와 티는 각각 작업을 처리합니다. 각자의 알고리즘을 수행
  2. 중복된 코드가 없다
  3. 알고리즘이 바뀌면 일일이 열어서 고쳐야한다.
  4. 클래스 구조상 새로운 음료를 추가하려면 많은 일을 해야한다.
  5. 알고리즘에 대한 지식과 구현 방법이 여러 클래스에 분산되어 있음

템플릿 메소드 적용 클래스(2-2 구현 내용)

  1. 추상 클래스에서 작업을 처리한다. 알고리즘을 독점적으로 처리
  2. 추상 클래스 덕분에 서브클래스에서 재사용 가능
  3. 알고리즘이 모여 있어 그 부분만 수정
  4. 다른 카페인 음료도 쉽게 추가 가능하다. 몇 가지 메소드만 추가한다면…
  5. 추상 클래스에 알고리즘이 집중되며 일부 구현만 서브 클래스에 의존한다.

템플릿 메소드 패턴 정의

8/Untitled.png

sourcemaking 이미지 참고

  • 메소드에서 알고리즘의 골격을 정의합니다. 알고리즘의 여러 단계 중 일부는 서브 클래스에서 구현할 수 있습니다. 템플릿 메소드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브클래스에서 특정 단계를 재정의할 수 있습니다.
  • 간단히 말하면 알고리즘의 틀을 만들기 위한 것
    • 틀(템플릿)이란? 일련의 단계들로 알고리즘을 정의한 메소드

2-4. 후크 구현하기

후크 정의

  • 후크란?
    • 추상 클래스에서 선언되는 메소드로 기본적인 내용만 있거나 아무 코드도 들어 있지 않은 메소드
    • 알고리즘 진행 상황을 후크로 상화에 따라 변경 가능하다.
    • 알고리즘 부분에서 필수적이지 않은 부분을 필요에 따라 서브 클래스에서 구현

카페인 제조법에 후크 적용하기

  • 커피에 후크 메소드 재정의 사용
    public abstract class CaffeineBeverageWithHook {
    
        final void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        if(customerWantsCondiments()) {  // 후크 적용 
            addCondiments();
        }
        }
    
        abstract void brew();
        abstract void addCondiments();
    
        final void boilWater(){
            System.out.println("물 끓이는 중");
        }
        final void pourInCup(){
            System.out.println("컵에 따르는 중");
        }
    
        boolean customerWantsCondiments(){  // 서브클래스 필요에 따라 오버라이드 가능 : 후크
            return true;
        }
    }
    
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    
    public class Coffee extends CaffeineBeverageWithHook {
        public void brew(){
            System.out.println("필터로 커피를 우려내는 중");
        }
    
        public void addCondiments(){
            System.out.println("설탕과 커피를 추가하는 중 ");
        }
    
        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("커피에 우유와 설탕 넣을까? (y/n)");
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            try{
                answer = in.readLine();
            } catch (IOException ioe){
                System.out.println("IO 오류");
            }
            if (answer == null){
                return "no";
            }
            return answer;
        }
    }

2-5. 헐리우드 원칙

헐리우드 원칙이란

먼저 연락하지 마세요. 저희가 연락 드리겠습니다. > 고수준 구성요소에서 저수준 구성요소에게 먼저 연락하지 마세요. 제가 먼저 필요하면 연락할게요! 라고 하는 것

  • 이 원칙을 사용하면 의존성 부패를 방지한다.
    • 의존성 부패란? 고수준 구성요소와 저수준 구성요소가 서로 의존됨
  • 템플릿 메소드에서는 고수준(추상 클래스)가 저수준(서브 클래스)가 필요할 때만 요청함.
  • 템플릿 메소드, 옵저버, 팩토리 메소드 적용

참고

  • Head first design patterns 책

소스 코드

  • https://github.com/JUNGEEYOU/Tamplate


TemplateTemplate method Pattern템플릿 패턴head first design patternsdesign pattern Share Tweet +1