ABOUT ME

Today
Yesterday
Total
  • Template Method Pattern
    디자인 패턴 2022. 2. 26. 18:24

     

    해당 책을 통해 공부한 내용을 정리하고 있습니다.

     


    정의

     

    파워포인트 템플릿 제공 사이트 - 출처: 미리캔버스

     

     템플릿 메소드 패턴에서는 말 그대로 ‘템플릿’을 제공하는 패턴이다. 예를 들어, 발표를 할 때 약간씩 내용을 수정하면 되는 파워포인트 템플릿등이 있는데 템플릿 메소드 패턴은 이것과 정확히 같은 동작을 한다. 정해져 있는 양식이 있고, 디테일은 서브클래스에서 템플릿 메소드를 구현해 사용하는 방식이다.

     

    책에서 소개하는 템플릿 메소드 패턴의 정의는 다음과 같다.

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

     


     

     

    UML과 구현

     한번 UML을 보자. templateMethod는 말 그대로 순서가 정해져 있는 템플릿이고, 안에 들어가 있는 primitive1() 메소드를 서브클래스에서 구현하여 사용하는 방식으로 진행된다.

     

    자바로 구현해보면 다음과 같이 나온다.

    public abstract class AbstractClass {
    
      // 상속한 클래스에서 변경하지 못하게 final로 선언했다.
      final public void templateMethod() {
        primitive1();
        primitive2();
      }
    
      // 메소드에서 디테일을 추가해줄 primitive 메소드
      // 서브클래스에서 상속하여 정의한다.
      public abstract void primitive1();
      public abstract void primitive2();
    }
    public class SubClass extends AbstractClass {
      // 추상 클래스 구현
      public void primitive1() {
        System.out.println("primitive1 is called");
      }
      public void primitive2() {
        System.out.println("primitive2 is called");
      }
    }
    public class Driver {
      public static void main(String[] args) {
        SubClass subClass = new SubClass();
        subClass.templateMethod(); // 템플릿 메소드 실행
      }
    }

     위와같이 추상화 되어 있는 템플릿 메소드를 서브 클래스에서 구현해주면 된다.

     

     


    훅(Hook)

     

     템플릿 메소드 패턴에는 ‘훅’이라는 패턴도 있다. 이를 설명하기 위해 카페에서 주문 받는 상황을 가정해보자. 이 카페에서는 차와 커피를 팔고 있고 템플릿 메소드 패턴으로 구현하려 한다. 그리고 손님이 원하면 커피에 우유와 설탕을 추가 할 수 있다고 했을 때 이를 어떻게 해결 할 수 있을까?

     

     훅을 사용하면 이런 변화하는 상황에서 템플릿 메소드를 처리 할 수 있다. 훅은 추상 클래스에서 선언되는 메소드이긴 하지만 기본적인 내용만 구현되어 있거나 아무 코드도 들어있지 않은 메소드이다. 이렇게하면 서브클래스 입장에서는 다양한 위치에서 알고리즘에 끼어 들 수 있게 된다. 코드와 함께 보자.

     

    public abstract class CaffeineBeverageWithHook {
    
        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;
        }
    }

     위의 코드에서 훅에 해당하는 내용은 customerWantsCondiments() 메소드로, 해당 메소드를 통해 우유와 설탕등을 넣는 분기를 처리 할 수 있도록 한 모습을 볼 수 있다.

    public class CoffeeWithHook extends CaffeineBeverageWithHook{
        @Override
        void brew() {
            System.out.println("필터로 커피를 우려내는 중");
        }
    
        @Override
        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.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;
        }
    }
    public class BeverageTestDrive {
    
        public static void main(String[] args) {
            CoffeeWithHook coffeeHook = new CoffeeWithHook();
    
            System.out.println("\n 커피 준비중...");
            coffeeHook.prepareRecipe();
        }
    }

    output

     고객의 대답에 따라, 우유와 설탕을 넣을 수도 있고 안 넣을 수도 있는 것을 볼 수 있다.

     

     

     다른 예제도 생각해보자. 이번에는 DB와 유저를 저장하는 로직을 연결하는 ORM을 생각해보자. 템플릿 패턴은, 데이터가 올바른지 확인하는 일, 저장하기 전에 해야하는 일과 문제가 없다면 데이터를 저장하는 일이 있다고 뽑아냈다고 하자. 이 뽑아낸 요구사항을 토대로 다음과 같이 클래스를 만들었다.

     

    이 UML을 토대로 구현해보자.

    public abstract class Record {
      public void save() {
        validate();
        beforeSave();
        afterSave();
        // ... saveLogic
      }
    
      public abstract validate();
    
      // 훅 메소드 
      public void beforeSave() {};
      public void afterSave() {};
    }
    public class User extends Record{
    
      public void validate() {
        // do something
      }
    
      public void beforeSave() {
        // do something
      }
    }

     위와 같은 방식으로 User 클래스에서 훅 메소드인 beforeSave()메소드 또는 afterSave()를 구현함으로써 해야 할 일들을 유연하게 지정해 줄 수 있게 된다.

     

     

     


    헐리우드 원칙 (HollyWood Principle)과 의존성 부패(Dependency rot)

     의존성 부패란 의존성이 복잡하게 꼬여있는 상황을 말한다. 어떤 고수준 인터페이스에서 저수준 요소에 의존하고, 저수준 요소에서 고수준 요소에 의존하고 또 그 고수준 요소는 어떤 요소에 의존하고 하는 방식으로 복잡하게 꼬여있는 상황을 의존성 부패라고 말한다.

     

     이를 해결하는 방법이 책에 친절하게 소개되어 있다. 바로 헐리우드 원칙 또는 제어의 역전(Inversion of Control) 원칙을 적용하는 것이다. 이는 저수준 구성요소에서 시스템에 접속을 할 수 있지만, 언제 어떤 식으로 그 구성요소들을 사용할지는 고수준 구성요소에서 결정하게 된다. 즉 고수준 구성요소에서 저수준 구성요소에게 “먼저 연락하지 마세요, 제가 먼저 연락 드리겠습니다”라고 얘기를 하는 것과 같다.

     

     이는 템플릿 메소드 패턴에 적용되어 있다. 저수준 요소인 서브 클래스가 시스템에 접근하여 로직을 바꿀 수는 있지만, 언제 호출되는 지는 추상 메소드가 정의되어 있는 추상 클래스에서 정하는 것을 볼 수 있다.

     

     

     


     

    References

    https://www.youtube.com/watch?v=7ocpwK9uesw 

     

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

    Adapter Pattern  (0) 2022.02.25
    Command Pattern  (0) 2022.02.23
    Factory Pattern  (0) 2022.02.14
    Decorator Pattern  (0) 2022.02.08
    Observer Pattern  (0) 2022.02.03
Designed by Tistory.