ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 템플릿 메서드(Template Method) 패턴
    언어/디자인패턴 2024. 5. 26. 22:31

     

    템플릿 메서드 패턴이란?

     

    • 여러 클래스에서 공통으로 사용하는 메서드를 템플릿화하여 상위 클래스에서 정의하고, 하위 클래스마다 세부 동작 사항을 다르게 구현하는 패턴이다.
    • 변하지 않는 기능은 상위 클래스에 만들어두고 자주 변경되어 확장할 기능은 하위 클래스에서 만들도록 하는 것이다.
    • 상속이라는 기술을 극대화하여, 알고리즘의 뼈대를 맞추는 것에 초점을 둔다. 
    • 공통된 부분에서 다른 부분 혹은 확장 가능한 부분을 제공하는 느낌! (공통을 따로 뺀다는 느낌보다 공통은 그대로 두고, 바뀌는 부분만 새로 만들도록 한다!)

    출처: https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%85%9C%ED%94%8C%EB%A6%BF-%EB%A9%94%EC%86%8C%EB%93%9CTemplate-Method-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90

     

    AbstractClass(추상 클래스) : 템플릿 메소드를 구현하고, 템플릿 메소드에 돌아가는 추상 메서드를 선언한다. 이 추상 메서드는 하위클래스 ConcreateClass에서 구현된다.

    ConcreteClass(구현 클래스): AbstractClass를 상속하고 추상 메서드를 구현한다. ConcreteClass에서 구현한 메소드는 AbstractClass의 템플릿 메소드에서 호출된다. 

    출처: https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%85%9C%ED%94%8C%EB%A6%BF-%EB%A9%94%EC%86%8C%EB%93%9CTemplate-Method-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90 ㅙㅐ

    hook메서드 

    • 부모의 템플릿 메서드의 영향이나 순서를 제어하고 싶을때 사용되는 메서드 형태를 말한다. 
    • step2()라는 메서드의 참과 거짓 여부에 따라 다음 스텝을 어떻게 이어갈지 지정한다. 이를 통해 자식 클래스에서 좀 더 유연하게 템플릿 메서드 알고리즘 로직을 다양화할 수 있다. 
    • 훅은 일반 메서드로 구현해여 선택적으로 오버라이드 하도록 한다. 
      • 이는 흐름제어용도가 아닌 하위 클래스 확장지점이 될 수도 있음을 의미 
    class Client {
       public static void main(String[] args) {
           // 1. 템플릿 메서드가 실행할 구현화한 하위 알고리즘 클래스 생성
           AbstractTemplate templateA = new ImplementationA();
    
           // 2. 템플릿 실행
           templateA.templateMethod();
       }
    }
    ]
    abstract class AbstractTemplate {
    
        // 템플릿 메소드 : 메서드 앞에 final 키워드를 붙이면 자식 클래스에서 오버라이딩이 불가능함.
    	// 자식 클래스에서 상위 템플릿을 오버라이딩해서 자기마음대로 바꾸도록 하는 행위를 원천 봉쇄
        public final void templateMethod() {
            // 상속하여 구현되면 실행될 메소드들
            step1();
            step2();
            
            if(hook()) { // 안의 로직을 실행하거나 실행하지 않음
                // ...
            }
            
            step3();
        }
    
        boolean hook() {
            return true;
        }
    
        // 상속하여 사용할 것이기 때문에 protected 접근제어자 설정
        protected abstract void step1();
        protected abstract void step2();
        protected abstract void step3();
    }
    class ImplementationA extends AbstractTemplate {
    
        @Override
        protected void step1() {}
    
        @Override
        protected void step2() {}
    
        @Override
        protected void step3() {}
    }
    
    class ImplementationB extends AbstractTemplate {
    
        @Override
        protected void step1() {}
    
        @Override
        protected void step2() {}
    
        @Override
        protected void step3() {}
    
        // hook 메소드를 오버라이드 해서 false로 하여 템플릿에서 마지막 로직이 실행되지 않도록 설정
        @Override
        protected boolean hook() {
            return false;
        }
    }

    템플릿 메서드 사용 시기 및 주의점

     

    사용시기 

    • 완전히 동일한(거의 비슷한)절차를 가진 코드 일부 코드는 절차 중 일부 과정의 구현만 다르고 나머진 똑같은 경우 즉, 알고리즘의 특정 단계만 확장하고, 전체 알고리즘이나 구조는 확장하지 않을 때 
      • DB혹은 LDAP를 사용해서 사용자를 인증하는 클래스가 있다고 가정해보자 
        • DB에서 사용자 인증하나 LDAP를 이용하나 사용자 정보 받고-> 인증확인하고 -> 실패 혹은 성공하면 어떻게할지 절차가 같다. 
    • 동일한 기능은 상위 클래스에서 정의하면서 확장, 변화가 필요한 부분만 하위 클래스에서 구현할 때

     

    주의점

    • 알고리즘의 제공된 골격에 유연성 제한될 수 있음
    • 구조가 복잡할수록 템플릿 로직 형태를 유지하기 어렵다.
    • 추상 메서드가 많아지면 클래스의 생성 및 관리가 어려워질 수 있다. 
    • 상위 클래스에서 선언된 추상 메소드를 구현시, 어느 타이밍에 호출되는지 로직을 이해해야한다.
    • 상위 클래스를 수정하면, 모든 서브 클래스가 수정될 수도 있다. 

     

    전략 패턴과 차이점 

    • 전략 패턴은 합성을 통해 해결 vs 템플릿 메서드 패턴은 상속을 통해 해결책을 제시 
    • 전략 패턴은 클라이언트와 객체 간의 결합이 느슨한 반면, 템플릿 메서드는 더 밀접하게 결합된다. 
    • 전략 패턴은 전체 알고리즘 자체를 변경할 수 있지만, 템플릿 메서드는 일부만 변경되고 나머지는 변경되지 않는다. (템플릿 종속성) 
    • 단일 상속만 가능한 자바에서는 전략 패턴이 더 자주 사용된다. 

     

    전략 패턴과 탬플릿 메서드 조합 

    • 템플릿 메서드와 전략 패턴을 함께 사용하면, 상속이 아닌 조립의 방식으로 템플릿 메서드 패턴을 활용할 수 있다.
    • 스프링 프레임워크의 Template로 끝나는 클래스들이 이를 적용한다. 
    • 이 클래스들은 템플릿 메서드를 실행할 때 변경되는 부분을 실행할 객체 파라미터를 통해 전달받는 방식으로 구현되어 있다. 
    public <T> T execute(TransactionCallBack<T> action){
    	
        //트랜잭션 상태
    	TransactionStatus status = this.transactionManager.getTransaction(this);
        T result;
        
        try{
        	result = action.dolnTransaction(status);
        }//..생략
        
        this.transactionManager.commit(status);
        return result;
    }
    • execute()메서드는 트랜잭션의 시작/커밋/롤백 등의 흐름을 제공하는 템플릿 메서드이다.
    • dolnTransaction 메서드가 변경 포인트 이다.
    • 앞서 본 템플릿 메서드와 차이점이 있는데
      • 하위 타입에서 재정의하지 않고,  전략을 파라미터로 전달받는다.
      • 따라서 TransactionTemplate의 execute() 메서드를 사용하는 코드는 dolnTransaction을 구현한 클래스를 넘겨야한다.  

     


    예시

     

    상황에 따라 다양한 연산 알고리즘 적용하기 

    • 파일에서 숫자값을 읽어와서 연산한 결과를 알려주는 기능을 구현해보자 
    class Client {
        public static void main(String[] args) {
            // 파일경로 설정
            FileProcessor fileProcessor = new FileProcessor("number.txt");
    
            // 덧셈한 결과값 얻기
            int result = fileProcessor.process();
            System.out.println(result);
        }
    }
    
    class FileProcessor {
        private String path; // 생성자로 부터 파일경로를 받아와 저장
    
        public FileProcessor(String path) {
            this.path = path;
        }
    
        public int process() {
            try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
                int result = 0;
                String line = null;
    
                // 파일에 있는 각 라인에 있는 숫자값들을 모두 덧셈
                while ((line = reader.readLine()) != null) {
                    result += Integer.parseInt(line);
                }
                return result;
    
            } catch (IOException e) {
                throw new IllegalArgumentException(path + "에 해당하는 파일이 없습니다.", e);
            }
        }
    }
    ]
    • 해당 로직에서 곱셈 혹은 나누는 추가 연산 기능이 필요하다고 하면 어떻게 해야할까?
    • 이럴 때 템플릿 메서드 패턴을 사용하면 좋다! 
      • 공통 부분 빼고, 계산하는 로직만 다르다! 
    abstract class FileProcessor {
        private String path; // 생성자로 부터 파일경로를 받아와 저장
    
        public FileProcessor(String path) {
            this.path = path;
        }
    
        // 템플릿 메소드 (오버라이딩 못하게 final 처리)
        public final int process() {
            try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
                int result = getResult();
                String line = null;
    
                while ((line = reader.readLine()) != null) {
                    result = caculate(result, Integer.parseInt(line));
                }
                return result;
    
            } catch (IOException e) {
                throw new IllegalArgumentException(path + "에 해당하는 파일이 없습니다.", e);
            }
        }
    
        protected abstract int caculate(int result, int number);
        protected abstract int getResult();
    }
    • 이와 같이 템플릿으로 변경하고, 하위 클래스에서 caculate와 getResult()만 구현하도록 한다.

     

    할리우드 원칙 준수 

     

    • 할리우드 원칙이란, 고수준 모듈(추상 클래스, 인터페이스)에 의존하고, 고수준 모듈에서 연락하라는 원칙이다.
    • 객체 끼리 이상하게 얽혀 의존성이 복잡해지는 것을 의존성 부패라고 하는데, 헐리우드 원칙을 적용하면 의존성 부패를 방지할 수 있다. 
    //메서드 명만 다르고 하는 짓은 똑같은 클래스 
    
    class LowerA {
        void print(int num) {
            System.out.println(num);
        }
    
        int calculate(int n1, int n2) {
            return n1 + n2;
        }
    }
    
    class LowerB {
        void echo(int variable) {
            System.out.println(variable);
        }
    
        int operation(int n1, int n2) {
            return n1 * n2;
        }
    }
    • 아래와 같이 상속을 활용할 수 있다. 
    abstract class Higher {
        void print(int num) {
            System.out.println(num);
        }
    
        abstract int calculate(int n1, int n2);
    }
    
    class LowerA extends Higher {
    
        @Override
        int calculate(int n1, int n2) {
            return n1 + n2;
        }
    }
    
    class LowerB extends Higher {
    
        @Override
        int calculate(int n1, int n2) {
            return n1 * n2;
        }
    }
    • 템플릿 메서드 패턴의 핵심은 추상 클래스를 통한 코드 통합과 고수준 의존 유도이다!
      • 도입에 말했듯 상속 기술의 극대화! 

    참고자료

     

    https://product.kyobobook.co.kr/detail/S000001062523

     

    객체 지향과 디자인 패턴 | 최범균 - 교보문고

    객체 지향과 디자인 패턴 | 개발자가 반드시 정복해야 할 『객체 지향과 디자인 패턴』. 자바나 C#과 같은 객체 지향 언어는 익혔지만 객체 지향 자체에 대한 이해가 부족한 개발자를 위해 가능

    product.kyobobook.co.kr

    https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%ED%85%9C%ED%94%8C%EB%A6%BF-%EB%A9%94%EC%86%8C%EB%93%9CTemplate-Method-%ED%8C%A8%ED%84%B4-%EC%A0%9C%EB%8C%80%EB%A1%9C-%EB%B0%B0%EC%9B%8C%EB%B3%B4%EC%9E%90

     

    💠 템플릿 메소드(Template Method) 패턴 - 완벽 마스터하기

    Template Method Pattern 템플릿 메서드(Template Method) 패턴은 여러 클래스에서 공통으로 사용하는 메서드를 템플릿화 하여 상위 클래스에서 정의하고, 하위 클래스마다 세부 동작 사항을 다르게 구현하

    inpa.tistory.com

     

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

    전략 패턴 (Strategy)  (0) 2024.05.26
Designed by Tistory.