따봉도치야 고마워

Head First Design Patterns : (8)템플릿 메소드 패턴 본문

프로그래밍/공부

Head First Design Patterns : (8)템플릿 메소드 패턴

따봉도치 2020. 9. 23. 14:50

템플릿 메소드 패턴

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

예제

  • 커피와 차를 만드는 클래스를 추상화 해보자
Coffee Tea
1. 물을 끓인다.
2. 끓는 물에 커피를 우려낸다.
3. 커피를 컵에 따른다.
4. 설탕과 우유를 추가한다.
1. 물을 끓인다.
2. 끓는 물에 차를 우려낸다.
3. 차를 컵에 따른다.
4. 레몬을 추가한다.
  • 기존 커피/차 클래스
public class Coffee{
    void prepareRecipe(){
    	boilWater();
        brewCoffeeGrinds();
        pourInCup();
        addSugarAndMilk();
    }
    
    public void boidWater(){ System.out.println("물 끓이는 중"); }
    public void brewCoffeeGrinds(){ System.out.println("커피 우려내는 중"); }
    public void pourInCup(){ System.out.println("컵에 따르는 중"); }
    public void addSugarAndMilk(){ System.out.println("설탕과 우유를 추가하는 중"); }
}

public class Tea{
    void prepareRecipe(){
    	boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }
    
    public void boidWater(){ System.out.println("물 끓이는 중"); }
    public void steepTeaBag(){ System.out.println("차를 우려내는 중"); }
    public void pourInCup(){ System.out.println("컵에 따르는 중"); }
    public void addLemon(){ System.out.println("레몬을 추가하는 중"); }
}

 

  • 부모에 prepareRecipe()를 추상메소드로 선언하고, 1번(boilWater)과 3번(pourInCup)을 정의한다 ?
  • 더 추상화 할 순 없을까?
    • 2, 4번도 '우려낸다' 와 '첨가물을 추가한다'로 추상화가 가능함.
    • 메소드 명을 일치시키면 prepareRecipe()메소드도 부모클래스에서 정의 가능.
public abstract class CaffeineBeverage{
    //음료 만드는 과정을 변경할 수 없게 final로 선언
    final void prepareRecipe(){
    	boilWater();
        brew();
        pourInCup();
        addCondiments();
    }
    
    //커피와 차가 다른 방식으로 처리하기 때문에 추상 메소드로 선언 (서브클래스에서 정의)
    abstract void brew();
    abstract void addCondiments();
    
    //커피와 차 둘 다 동일한 부분이기 때문에 여기서 정의
    void boidWater(){ System.out.println("물 끓이는 중"); }
    void pourInCup(){ System.out.println("컵에 따르는 중"); }
}

 

  • 커피와 차 클래스에선 위의 클래스를 상속받아 brew(), addCondiments() 메소드만 정의해주면 됨.
  • 여기서 prepareRecipe()가 템플릿 메소드 : 어떤 알고리즘에 대한 템플릿(틀) 역할을 함 

 

개선점

  • CaffeineBeverage에서 알고리즘을 독점해, 알고리즘에 대한 지식과 구현 방법이 분산되지 않음.
  • 일부 구현만 서브 클래스에 의존
  • 서브 클래스에서 코드를 재사용할 수 있음
  • 음료 추가 및 수정 시에도 비교적 간단함

템플릿 메소드와 후크(hook)

후크(hook) : 추상클래스에서 선언되는 메소드지만 기본적인 내용만 구현 or 아무 코드도 들어있지 않은 메소드

서브클래스 입장에선 다양한 위치에서 알고리즘에 끼어들 수 있음

  • 알고리즘에서 필수적이지 않은 부분
  • 템플릿 메소드에서 앞으로 일어날 일, 막 일어난 일에 대해 서브클래스에서 반응할 기회를 제공하기 위한 용도
  • 진행되는 작업에 대한 결정을 내리는 기능

ex.

    final void prepareRecipe(){
    	boilWater();
        brew();
        pourInCup();
        if(customerWantsCondiments()){
        	addCondiments();
        }
    }
    
    boolean customerWantsCondiments(){
    	return true;
    }    
    
  • customerWantsCondiments()가 후크
  • 서브클래스에서 필요에 따라 내용을 변경해 알고리즘에 영향을 끼칠 수 있음

 

디자인 원칙

  • 헐리우드 원칙 - "먼저 연락하지 마세요. 저희가 연락드리겠습니다"
  • 의존성 부패(dependency rot, 의존성이 복잡하게 꼬여있은 것)를 방지 할 수 있음.
  • 해당 원칙을 사용하면 저수준 구성요소에서 시스템에 접속할 수 있지만, 언제 어떤식으로 구성요소를 사용할지는 고수준 구성요소에서 결정하게 됨.
  • 즉 고수준 구성요소에서 저수준에게 하는 말이라고 생각 

 

Q&A

1. 템플릿을 만들 때 언제 추상메소드를 사용하고 언제 후크를 사용하는지

  • 서브클래스에서 알고리즘의 특정 단계를 제공해야할 때 추상 메소드
  • 알고리즘의 특정 부분이 선택적으로 적용된다든가 하는 경우에 후크 - 후크는 선택적 구현이 가능하니까

2. 추상 메소드가 너무 많아지면 안 좋을 것 같은데

  • 맞음. 따라서 알고리즘의 단계를 적절히 나누고, 필수적이지 않은 부분은 후크로 구현하면 부담이 줄음

3. 의존성 뒤집기 원칙과 헐리우드 원칙

둘 다 객체를 분리시킨다는 하나의 목표를 공유하지만

  • 의존성 뒤집기는 구상클래스 사용을 줄이고, 추상화된 것을 사용한다는 원칙 -> 의존성을 피하는데 있어 더 강하고 일반적
  • 헐리우드 원칙은 저수준 구성요소와 고수준 계층 사이에 의존성이 없는 프레임워크를 구축해줌

 

 

+

 

자바 Arrays.Sort()

  • sort() 내부에서 부르는 MergeSort()가 템플릿 메소드
  • MergeSort()에선 Arrays 클래스에 정의된 구상 메소드인 swap()서브클래스에서 구현해야하는 compareTo()를 사용
  • Arrays의 서브 클래스를 만들 수 없는데 어떻게 하지?
    • sort()를 정적 메소드로 만들어 모든 배열에서 사용 가능
    • but, 각 요소에 compareTo() 메소드가 구현 되어 있는지 알아낼 방법이 필요
    • -> Comparable 인터페이스 구현해 compareTo() 메소드 구현

 

Q. 이것도 템플릿 메소드라고 할 수 있는지 (상속을 사용하지 않는데)

  • 실전에서 패턴을 적용하는 방법이 교재에 나와있는 방법과 완전히 같을 순 없음
  • 게다가 어떤 배열에서도 정렬 기능을 사용할 수 있어야하기 때문에 정적메소드를 정의하고, 각 객체가 정렬에 대해 구현하도록 해 오히려 더 유연해짐

Q. 스트래티지 패턴과 더 가까워 보이는데?

  • 스트래티지 패턴에선 구성을 위해 사용하는 클래스에서 알고리즘을 완전히 구현해야 되는데 MergeSort()는 불완전(compareTo)

 

+ 스윙 JFrame의 update() : paint()라는 후크 메소드를 호출함

+ Applet : init(), start(), stop(), destroy(), paint() 각 시점에 실행되는 후크 메소드들이 있음

 


템플릿 메소드 패턴 vs 스트래티지 패턴

  템플릿 메소드 패턴 스트래티지 패턴
특징 알고리즘의 개요를 정의
서브클래스에서 일부 작업을 구현
상속 사용
알고리즘 군을 캡슐화해 그것들을 서로 바꿔가며 사용 가능
장단점 프레임워크를 만들기 좋음
각 단계별 다른 구현을 사용하면서도 구조는 유지됨
코드 중복이 적음
구성을 사용해 더 유연하고 의존성이 적음

+ 팩토리 메소드 패턴은 특화된 템플릿 메소드 패턴이라고 불림

Comments