본문 바로가기

JAVA/Design Patterns

8.템플릿 메서드 패턴

반응형

템플릿 메서드 패턴


정의

- 메서드에서 알고리즘의 골격을 정의한다.

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

템플릿 메서드를 이용하면 알고리즘의 구조는 그대로 유지하면서 서브 클래스에서 특정 단계를 재정의 할 수 있다.


알고리즘의 틀을 만들기 위한 것.

틀(템플릿)이란 일련의 단계들로 알고리즘을 정의한 메서드이다.

여러 단계 가운데 하나 이상의 추상 메서드로 정의되며, 그 추상 메서드는 서브클래스에서 구현된다.

서브클래스에서 일부분을 구현할 수 있도록 하면서 알고리즘의 구조는 바꾸지 않아도 되도록 할 수 있다




예) 커피/차 주문

공통적인 행동과 각각 다른 행동들을 분리하여 상위 추상클래스 만들어서 사용


package patterns.template;


public class Coffee {


    void prepareRecipe() {

        boilWater();

        brewCoffeGrindes();

        pourInCunp();

        addSugarAndMilk();

    }


    private void addSugarAndMilk() {

        System.out.println("설탕과 우유를 추가하는 중");

    }


    private void pourInCunp() {

        System.out.println("컵에 따르는 중");

    }


    private void brewCoffeGrindes() {

        System.out.println("필터를 통해서 커피를 우려내는 중");

    }


    private void boilWater() {

        System.out.println("물 끓이는 중");

    }


}



public class Tea {


    void prepareRecipe() {

        boilWater();

        steepTeaBag();

        pourInCunp();

        addLemon();

    }


    private void steepTeaBag() {

        System.out.println("차를 우려내는 중");

    }


    private void pourInCunp() {

        System.out.println("컵에 따르는 중");

    }


    private void addLemon() {

        System.out.println("레몬을 추가하는 중");

    }


    private void boilWater() {

        System.out.println("물 끓이는 중");

    }

}



package patterns.template;


public abstract class CaffeineBeverage {


    // final 선언 메서드를 오버라이드해서 아무렇게나 음료를 만들지 못하도록하기 위함

    final void prepareRecipe() {

        boilWater();

        brew();

        pourInCunp();

        addCondiments();

    }


    private void pourInCunp() {

        System.out.println("컵에 따르는 중...");

    }


    private void boilWater() {

        System.out.println("물 끓인는 중...");

    }


    //서로 다른 방식으로 처리하기 위한 abstract

    protected abstract void addCondiments();


    protected abstract void brew();


}



package patterns.template;


public class Coffee extends CaffeineBeverage{


    @Override

    protected void addCondiments() {

        System.out.println("설탕과 우유를 추가하는 중");

    }


    @Override

    protected void brew() {

        System.out.println("필터로 커피를 내리는중");

    }


}


public class Tea extends CaffeineBeverage{

    @Override

    protected void addCondiments() {

        System.out.println("레몬을 추가하는 중");

    }


    @Override

    protected void brew() {

        System.out.println("차를 우려내는 중");

    }

}


package patterns.template;


public class BeverageTestDriven {

    public static void main(String[] args) {

        Coffee coffee = new Coffee();

        coffee.prepareRecipe();


        Tea tea = new Tea();

        tea.prepareRecipe();


    }

}




package patterns.template;


public abstract class AbstractClass {

    

    final void templateMethod() {

        primitiveOperation1();

        primitiveOperation2();

        concrecteOperation();

        hook();

    }


    protected abstract void primitiveOperation1(); //서브클래스에서 구현

    

    protected abstract void primitiveOperation2();


    private final void concrecteOperation() {

    //구상단계는 추상 클래스 내에서 정의.

    //final로 선언되었기 때문에 서브 클래스에서 오버라이드 할 수 없음.

    //템플릿 메서드에서 직접호출할 수도 있고, 서브클래스에서 호출해서 사용할 수 도 있음.

    }


    private void hook() {

    // 구상메서드긴 한데 아묵서도 하지 않는다.

    //기본적으로 아무것도 하지 않는 구상 메서드를 정의할 수도 있다.

    //서브클래스에 오버라이드할 수도 있지만, 반드시 해야되는건 아님.   

    }


}



후크 활용의 예)


package patterns.template;


public abstract class CaffeinBeverageWithHook {


    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;

    }

}



package patterns.template;


import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;


public class CoffeeWithHook extends CaffeinBeverageWithHook {


    @Override

    public void brew() {

        System.out.println("필터로 커피를 내리는중...");

    }


    @Override

    public void addCondiments() {

        System.out.println("우유와 설탕을 추가하는중...");

    }


    @Override

    public boolean customerWantsCondiments() {

        String answer = getUserInput();

        if (answer.toLowerCase().startsWith("y")) {

            return true;

        } else {

            return false;

        }

    }


    private String getUserInput() {

        String answer  = null;


        System.out.println("커피에 우유와 설탕을 넣겠습니까?");


        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));


        try {

            answer = in.readLine();

        } catch (IOException ex) {

            System.err.println("IO 오류");

        }

        if (answer == null) {

            return "no";

        }

        return answer;

    }


}


후크를 써서 알고리즘의 진행 상황에 따라 변경시키는 방법




헐리우드원칙

먼저연락하지마세요. 저희가 연락드리겠습니다.


의존성부패(dependency rot) 방지를 할 수 있다.

고수준 구성요소가 저수준 구성요소에 의존하고, 그 저수준 구성요소는 다시 고수준 구성요소에 의존하고, 그 고수준 구성요소는 다시 또 다른 구성요소에 의존하고, 그 다른 구성요소는 또 저수준 구성요소에 의존하는 것과 같은 식으로 의존성이 복잡하게 꼬여있는 것을 의존성의 부패라고 부른다.

이렇게 의존성이 부패되면 시스템이 어떤식으로 디자인된것인지 거의 아무도 알아볼수 없게된다.


저수준 구성요소에서 시스템에 접속할 수 있지만,언제 어떤식으로 그 구성요소들을 사용할지는 고수준 구성요소에서 결정하게 된다.

즉 고수준 구성요소에서 저수준 구성요소에게 "먼저연락하지마세요! 제가 먼저 연락드리겠습니다."라고 얘기하는 것과 같다.



템플릿 메서드 패턴 - 알고리즘의 일부단계를 구현하는 것을 서브클래스에서 처리한다.


스트래티지 패턴 - 바꿔슬수 있는 행동을 캡슐화하고, 어떤 행동을 사용할지는 서브클래스에 맡긴다.


팩토리 메서드 패턴 - 어떤 구상 클래스를 생성할지를 서브클래스에서 결정한다.




Arrays 클래스의 정렬


    public static void sort(Object[] a, int fromIndex, int toIndex) {

        rangeCheck(a.length, fromIndex, toIndex);

        if (LegacyMergeSort.userRequested)

            legacyMergeSort(a, fromIndex, toIndex);

        else

            ComparableTimSort.sort(a, fromIndex, toIndex, null, 0, 0);

    }

    

/** To be removed in a future release. */

    private static void legacyMergeSort(Object[] a,

                                        int fromIndex, int toIndex) {

        Object[] aux = copyOfRange(a, fromIndex, toIndex);

        mergeSort(aux, a, fromIndex, toIndex, -fromIndex);

    }

    

    

    @SuppressWarnings({"unchecked", "rawtypes"})

    private static void mergeSort(Object[] src,

                                  Object[] dest,

                                  int low,

                                  int high,

                                  int off) {

        int length = high - low;


        // Insertion sort on smallest arrays

        if (length < INSERTIONSORT_THRESHOLD) {

            for (int i=low; i<high; i++)

                for (int j=i; j>low &&

                         ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)

                    swap(dest, j, j-1);

            return;

        }


        // Recursively sort halves of dest into src

        int destLow  = low;

        int destHigh = high;

        low  += off;

        high += off;

        int mid = (low + high) >>> 1;

        mergeSort(dest, src, low, mid, -off);

        mergeSort(dest, src, mid, high, -off);


        // If list is already sorted, just copy from src to dest.  This is an

        // optimization that results in faster sorts for nearly ordered lists.

        if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {

            System.arraycopy(src, low, dest, destLow, length);

            return;

        }


        // Merge sorted halves (now in src) into dest

        for(int i = destLow, p = low, q = mid; i < destHigh; i++) {

            if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)

                dest[i] = src[p++];

            else

                dest[i] = src[q++];

        }

    }

    

    

mergeSort() 템플릿 메서드

메서드에는 정렬 알고리즘이 들어있으며, CompareTo()메서드에 의해 결과가 결정된다.

package patterns.template;


public class Duck implements Comparable {

    private String name;

    private int weight;


    public Duck(String name, int weight) {

        this.name = name;

        this.weight = weight;

    }


    @Override

    public String toString() {

        return name + ", 체중 " + weight;

    }


    @Override

    public int compareTo(Object o) {

        Duck otherDuck = (Duck) o;

        if (this.weight < otherDuck.weight) {

            return -1;

        } else if (this.weight == otherDuck.weight) {

            return 0;

        } else {

            return 1;

        }

    }

}

    

package patterns.template;


import java.util.Arrays;


public class TemplateTestDriven {

    public static void main(String[] args) {

        Duck[] ducks = {

                new Duck("Daffy", 8),

                new Duck("Dewey", 2),

                new Duck("Howard", 7),

                new Duck("Louie", 2),

                new Duck("Donald", 10),

                new Duck("Huey", 2)

        };


        System.out.println("정렬 전:");

        display(ducks);


        Arrays.sort(ducks);


        System.out.println("정렬 후:");

        display(ducks);

    }


    private static void display(Duck[] ducks) {

        for (int i=0; i<ducks.length; i++) {

            System.out.println(ducks[i]);

        }

    }

}



Arrays.sort(ducks);


sort() 메서드에서 알고리즘을 처리한다. 

어떤 클래스에서도 이과정을 고칠수 없다.

sort()에서 Comparable 인터페이스에서 제공하는 compareTo() 메서드에 의존한다.


Arrays.sort()는 정적메서드를 정의한 다음, 알고리즘에서 대소비교하는 부분은 정렬된 객체에서 구현하도록 만듬게 함.

교과서적인 템플릿 메서드라고 할순없지만, sort()메서드 구현 자체는 템플릿 메서드 패턴의 기본 정신을 충실하게 따르고 있다.

이 알고리즘을 쓰기 위해서 반드시 Arrays의 서브클래스를 만들어야하는 제약 조건을 없앰으로써 오히려 더 유연하면서 유용한 정렬 메서드가 만들어질 수 있음


핵심정리

- 템플릿 메서드에서는 알고리즘의 단계를 정의하는데, 일부 단계는 서브클래스에서 구현하도록 할 수 있다.

- 템플릿 메서드 패턴은 코드 재사용에 크게 도움이 된다.

- 템플릿 메서드가 들어있는 추상 클래스에서는 구상 메서드, 추상메서드, 후크를 정의할 수 있다.

- 추상 메서드는 서브클래스에서 정의한다.

- 후크는 추상클래스에 들어있는, 아무일도 하지 않거나 기본행동을 정의하는 메서드로, 서브클래스에서 오버라이드할 수 있다.

- 서브클래스에서 템플릿메서드에 들어있는 알고리즘을 함부로 바꾸지 못하게 하고 싶다면 템플릿 메서드를 final로 선언하면된다.

- 헐리우드 원칙에 의하면, 저수준 모듈을 언제 어떻게 호출할지는 고수준 모듈에서 결정하는 것이 좋다.

- 템플릿 메서드 패턴은 실전에서도 꽤 자주 쓰이지만, 반드시 교과서적인 방식으로 적용되지 않는다.

- 스트래티지 패턴과 템플릿 메서드 패턴은 모두 알고리즘을 캡슐화하는 패턴이지만, 전자에서는 상속을, 후자에서는 구성을 이용한다.

- 팩토리 메서드 패턴은 특화된 템플릿 메서드 패턴이다.


출처 - Head First Design Patterns 저자- 에릭 프리먼, 엘리자베스 프리먼, 케이시 시에라, 버트 베이츠

반응형

'JAVA > Design Patterns' 카테고리의 다른 글

9.컴포지트 패턴  (0) 2018.12.13
9.이터레이터 패턴  (0) 2018.12.13
7.어댑터 패턴 / 퍼사드 패턴  (0) 2018.12.10
6.커맨드 패턴  (0) 2018.12.06
5. 싱글톤 패턴  (0) 2018.12.06