ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 행위패턴 > 템플릿 메서드 *
    기타/디자인패턴 2021. 2. 12. 15:42

     

    템플릿은 틀을 의미한다. 템플릿 메서드는 알고리즘의 틀을 만들기 위한 것이다.

    템플릿 메서드 패턴에서는 알고리즘의 골격을 정의한다.  

    템플릿 메서드는 일련의 단계들로 알고리즘을 정의한 메서드를 의미한다.

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

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

     

    커피와 차를 만드는 과정이 각각 아래와 같다.

    커피 => 1.물을 끓인다. 2. 커피를 우린다. 3. 커피를 컵에 4. 설탕과 우유 추가

    차 => 1.물을 끓인다. 2. 차를 우린다. 3. 차를 컵에 4. 레몬 추가 

     

    이 때 일반화를 통해 공통으로 사용 가능한 부분을 추상화한 클래스를 만든다.

    "1. 끓인다.", "3. 컵에"와 같은 완전히 동일한 행동이 있고, 

    조금 다른  "2. 우린다.","4. 재료 추가" 가 있다.

     

    조금 다른 부분은 각 서브 클래스에서 구체적인 행동을 처리하도록 하고 완전히 같은 역할은 추상클래스에서 처리한다.

    prepareRecipe 을 호출했을 때 어떤 메서드는 CaffeineBeverage 추상클래스 내에서 , 어떤 매서드는 서브 클래스에서 처리된다. 

     

    public abstract class CaffeineBeverage {
    
        final void prepareRecipe(){ //템플릿메서드, 서브클래스에서 재정의 불가하도록 final 선언
            boilWater();
            brew();
            pourInCup();
            addCondiments();
        }    
    
        private void pourInCup() {
            System.out.println("컵으로~");
        }
    
        private void boilWater() {
            System.out.println("보글보글");
        }
    
    //각 서브 클래스에서 처리할 메서드는 abstract 으로 선언
        abstract void brew(); //차 or 커피를 우린다.
        abstract void addCondiments(); //각 음료에 맞는 재료를 추가한다.    
    }

     

    prepareRececipt은 여러 알고리즘(메서드)를 관리하는(템플릿) 메서드로, 

    prepareRececipt 을 템플릿 메서드라고 한다.

     

    만약 커피와 차를 중복 코드를  가진 각각의 클래스로 생성했다면 

    - 알고리즘이 변경될 때 동일한 알고리즘을 가진 서브클래스를 일일이 찾아내서 수정해야하고, 새로운 음료가 추가될 때마다 할 일이 더 많다.

    - 알고리즘에 대한 지식과 구현 방법이 여러 클래스에 분산되어 있다.

     

    템플릿 메서드 패턴을 적용한 CaffeineBeverage > prepareRececipt 클래스는

    CaffeineBeverage 에서 알고리즘을 독점하고 있는 구조로, 수정할 때 한 군데만 수정이 가능하며, 

    서브클래스에서 코드 재사용이 가능해진다. 새로운 음료가 추가될 때 몇가지 다른 메서드만 추가/재정의 해주면 된다.

     

    *템플릿 메서드와 후크

    후크는 추상클래스에 선언된 메서드로, 기본적인 내용만 있거나 아무 것도 하지 않는 메서드이다.

    서브클래스 입장에서는 후크를 이용해 다양한 위치에서 알고리즘에 끼어들 수도 있고 무시할 수도 있다.

    public abstract class CaffeineBeverage {
        final void prepareRecipe(){
            boilWater();
            brew();
            pourInCup();
            if(customerWantsCondiments()){ //후크 메서드
                addCondiments();
            }
        }    
    
        private void pourInCup() {
            System.out.println("컵으로~");
        }
    
        private void boilWater() {
            System.out.println("보글보글");
        }
    
        abstract void brew();
        abstract void addCondiments();    
    
    //후크 메서드
        boolean customerWantsCondiments(){
            return true;
        }
    
    }
    public class Coffee extends CaffeineBeverage{
    
        @Override
        void brew() {
            System.out.println("coffee brew");        
        }
    
        @Override
        void addCondiments() {
            System.out.println("add sugar");        
        }
    
        @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 e) {
                e.printStackTrace();
            }
            if(answer == null){
                return "no";
            }        
            return answer;
        }

     

    이런 식으로 후크는 알고리즘에서 필수적이지 않은 부분을 필요에 따라 서브클래스에 선택적으로 구현할 수 있도록 할 수 있다.

     

    또, 템플릿 메서드에서 앞으로 일어날 일 또는 방금 일어난 일에 대해 서브 클래스에서 반응할 기회를 제공하기 위한 용도로도 쓰일 수 있다.

    예를 들어, 내부적으로 어떤 목록을 재정렬한 후에 서브 클래스에서 어떤 작업을(화면상에 표시되는 내용을 다시 보여주는 등) 수행하도록 하고 싶은 경우에 justReOrderedList 같은 이름을 가진 후크 메서드를 쓸 수도 있다.

    서브클래스에 추상 클래스에서 진행되는 작업에 대한 결정을 내리는 기능을 부여하기 위한 용도로 후크를 사용할 수 있다.

     

    추상 메서드가 너무 많으면 서브클래스에서 일일이 구현하기 불편하고 불필요할 수 있기 때문에 

    템플릿 메서드를 만들 때 잘 고려해서 만들어야 하고 필수적이지 않은 부분은 후크로 구현하면 그런 부담을 줄일 수 있다.

     

    * 의존성 부패와 헐리우드 원칙

    어떤 고수준 구성요소가 저수준 구성요소에 의존하고, 그 저수준 구성요소가 다시 고수준 구성요소에 의존하고, 고수준 구성요소는 또 다른 구성요소에 의존하는 등 의존성이 복잡하게 꼬여있는 것을 의존성 부패라고 한다.

    헐리우드 원칙은 "내가 먼저 연락하지 전까지 먼저 연락하지마"라는 것으로 저수준 구성요소에서 시스템에 접속을 할 수 있지만, 구성요소들을 사용하는 것은 고수준에서 결정한다는 것이다.

     

    여기서 고수준 구성요소는 CaffeineBeverage로 클라이언트에서 Tea나 Coffee 같은 저수준 구상클래스가 아닌 CaffeineBeverage 에 의존하므로써 전체 시스템의 의존성이 줄어든다.

    CaffeineBeverage는 음료생성방법의 알고리즘을 장악하고 있고, 메서드 구현이 필요할 때만 서브클래스를 호출한다.

    서브클래스는 자질구레한 메서드를 구현한 클래스일 뿐이고 호출 당하기 전까지 절대 추상 클래스를 직접 호출하지 않는다.

     

    템플릿 메서드외에 팩토리 메서드, 옵저버도 이 헐리우드 원칙을 활용하는 패턴들이다.

     

    * 의존성뒤집기와 헐리우드 원칙

    의존성뒤집기는 구상 클래스 사용을 줄이고 대신 추상화된 것을 사용해야한다는 원칙으로 

    객체를 분리시킨다는 목표는 헐리우드 원칙과 동일하지만, 의존성을 피하는 방법에 있어 의존성 뒤집기 원칙이 더 강하고 일반적인 내용을 담고 있다.

    헐리우드 원칙은 저수준 구성요소들을 다양하게 사용할 수 있으면서도, 다른 클래스가 구성요소에 너무 의존하지 않게 만들어주는 디자인이다.

     

     

    실전에서도 템플릿 메서드를 많이 사용되지만 형태가 달라 템플릿 메서드 패턴으로 인지하지 못하는 경우들이 있다.

    예로 Arrays 의 sort 는 템플릿 메서드이다.

    sort 는 알고리즘을 독점하고 있으며 내부 알고리즘이 변경될 때 sort 부분만 수정하면 된다.

     

    자바 Arrays의 서브클래스를 만들 수 없지만 (Arrays 생성자 private, 상속불가),

    어떤 배열에서도 정렬기능을 사용할 수 있도록 만들어야 한다.

    그래서 정적 메서드를 정의한 후, 알고리즘 대소비교는 정렬될 객체에서 구현하도록 만든 것이다. (Comparable 구현필수이 알고리즘을 쓰기 위해 반드시 Arrays 의 서브클래스를 만들어야한다는 제약조건을 없앰으로써 오히려 더 유연하면서 유용한 정렬 메서드를 만들었다. 

     

    //Arrays.java
    
    public static void sort(Object[] a) {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a);
            else
                ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
    }
    
     /** To be removed in a future release. */
        private static void legacyMergeSort(Object[] a) {
            Object[] aux = a.clone();
            mergeSort(aux, a, 0, a.length, 0);
        }
    // ComparableTimSort.java
    static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
            assert a != null && lo >= 0 && lo <= hi && hi <= a.length;
    
            int nRemaining  = hi - lo;
            if (nRemaining < 2)
                return;  // Arrays of size 0 and 1 are always sorted
    
            // If array is small, do a "mini-TimSort" with no merges
            if (nRemaining < MIN_MERGE) {
                int initRunLen = countRunAndMakeAscending(a, lo, hi);
                binarySort(a, lo, hi, lo + initRunLen);
                return;
            }
    
            /**
             * March over the array once, left to right, finding natural runs,
             * extending short natural runs to minRun elements, and merging runs
             * to maintain stack invariant.
             */
            ComparableTimSort ts = new ComparableTimSort(a, work, workBase, workLen);
            int minRun = minRunLength(nRemaining);
            do {
                // Identify next run
                int runLen = countRunAndMakeAscending(a, lo, hi);
    
                // If run is short, extend to min(minRun, nRemaining)
                if (runLen < minRun) {
                    int force = nRemaining <= minRun ? nRemaining : minRun;
                    binarySort(a, lo, lo + force, lo + runLen);
                    runLen = force;
                }
    
                // Push run onto pending-run stack, and maybe merge
                ts.pushRun(lo, runLen);
                ts.mergeCollapse();
    
                // Advance to find next run
                lo += runLen;
                nRemaining -= runLen;
            } while (nRemaining != 0);
    
            // Merge all remaining runs to complete sort
            assert lo == hi;
            ts.mergeForceCollapse();
            assert ts.stackSize == 1;
        }

     

     

    * 템플릿 메서드 :

    알고리즘의 일부 단계를 구현하는 것을 서브클래스에서 처리한다. 상속을 이용한다.

     

    *스트래티지 메서드 :

    바꿔쓸 수 있는 행동을 캡슐화하고, 어떤 행동을 할지 서브클래스에 맡긴다. 객체의 구성을 이용한다.

     

    *팩토리 메서드 :

    어떤 구상 클래스를 생성할지를 서브클래스에서 결정한다.

     

    반응형

    '기타 > 디자인패턴' 카테고리의 다른 글

    컴파운드 패턴  (0) 2021.09.23
    싱글톤 패턴 *  (0) 2021.09.14
    행위패턴 > 상태(State) *  (0) 2021.02.12
    행위패턴 > 옵저버 *  (0) 2021.02.12
    행위패턴 > 이터레이터 *  (0) 2021.02.11
Designed by Tistory.