ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring Core - 템플릿 메서드 패턴(시작, 예제1, 예제2, 정의)
    FrameWork/Spring&Spring-boot 2024. 3. 6. 10:38

    템플릿 메서드 패턴 - 시작

     

    로그 추적기 도입 전 - V0코드

      //OrderControllerV0 코드
      @GetMapping("/v0/order")
        public String createOrder(String itemId){
                orderService.orderItem(itemId);
            return "ok";
        }
     
     //OrderServiceV0 코드
     public void orderItem(String itemId){
            orderRepository.save(itemId);
        }

     

     

    로그 추적기 도입 후 - V3코드

    //OrderControllerV3코드
    @GetMapping("/v3/order")
        public String createOrder(String itemId){
            TraceStatus status = null;
            try {
                status = trace.begin("OrderController.createOrder()");
                orderService.orderItem(itemId);
                trace.end(status);
                return "ok";
            }catch (Exception e){
                trace.exception(status,e);
                throw e; //예외를 꼭 다시 던저주어야 한다.
            }
    
        }
     
    //OrderServiceV3코드
    public void orderItem(String itemId){
    
            TraceStatus status = null;
            try {
                status = trace.begin("OrderService.orderItem()");
                orderRepository.save(itemId); //핵심 기능
                trace.end(status);
            }catch (Exception e){
                trace.exception(status,e);
                throw e;
            }
        }

     

    V0 코드와 비교해서 V3 코드를 보자

    V0 해당 메서드가 실제 처리해야 하는 핵심 기능만 깔끔하게 남아있다. 반면에 V3에는 핵심 기능보다 로그를 출력해야 하는 부가 기능코드가 훨씬 더 많고 복잡하다.

     

     

    핵심 기능 vs 부가 기능

    • 핵심 기능 은 해당 객체가 제공하는 고유의 기능이다. 예를 들어서 orderService의 핵심 기능은 주문 로직이다. 메서드 단위로 보면 orderService.orderItem()의 핵심 기능은 주문 데이터를 저장하기 위해 리포지토리를 호출하는 orderRepository.save(itemId)코드가 핵심 기능이다.
    • 부가 기능 은 핵심 기능을 보조하기 위해 제공되는 기능이다. 예를 들어서 로그 추적 로직, 트랜잭션 기능이 있다. 이런 부가 기능은 단독으로 사용되지는 않고, 핵심 기능과 함께 사용된다. 예를 들어서 로그 추적 기능은 어떤 핵심 기능이 호출되었는지 로그를 남기기위해 사용한다. 그러닌까 핵심 기능을 보조하기 위해 존재한다.

     

    V3코드를 유심히 잘 살펴보면 다음과 같이 동일한 패턴이 있다.

    TraceStatus status = null;
    
    try{
        status = trace.begin("message");
        //핵심 기능 호출
        trace.end(status);
    }catch(Exception e){
    	trace.exception(status, e);
        throw e;
    }

     

    Controller, Service, Repository의 코드를 잘 보면, 로그 추적기를 사용하는 구조는 모두 동일하다. 중간에 핵심 기능을 사용하는 코드만 다를 뿐이다.

    부가 기능과 관련된 코드가 중복이므로 중복을 별도의 메서드로 뽑아내면 될거 같다. 그런데 try ~ catch는 물론이고, 핵심기능 부분이 중간에 있어서 단순하게 메서드로 추출하는 것은 어렵다.

     

     

    ⭐변하는 것과 변하지 않는 것을 분리

    좋은 설계는 변한는 것과 변하지 않는 것을 분리하는 것이다.

    여기서 핵심 기능 부분은 변하고, 로그 추적기를 사용하는 부분은 변하지 않는 부분이다.

    이 둘을 분리해서 모듈화해야 한다.

     

    템플릿 메서드 패턴(Template Method Pattern)은 이런 문제를 해결하는 디자인 패턴이다.

     


    템플릿 메서드 패턴 - 예제1

     

    TemplateMethodTest

    @Slf4j
    public class TemplateMethodTest {
    
        @Test
        void templateMethodV0(){
            logic1();
            logic2();
        }
    
        private void logic1(){
            long startTime = System.currentTimeMillis();
            //비지니스 로직 실행
            log.info("비지니스 로직1 실행");
            //비지니스 로직 종료
            long endTime = System.currentTimeMillis();
            long resultTime = endTime - startTime;
            log.info("resultTime = {}",resultTime);
        }
    
        private void logic2(){
            long startTime = System.currentTimeMillis();
            //비지니스 로직 실행
            log.info("비지니스 로직1 실행");
            //비지니스 로직 종료
            long endTime = System.currentTimeMillis();
            long resultTime = endTime - startTime;
            log.info("resultTime = {}",resultTime);
        }
    }

     

    logic1(), logic2() 를 호출하는 단순한 테스트 코드이다.

     

    실행결과

     

    logic1() logic2() 는 시간을 측정하는 부분과 비지니스 로직을 실행하는 부분이 함께 존재한다.

    • 변하는 부분: 비지니스 로직
    • 변하지 않는 부분: 시간 측정

     

    이제 템플릿 메서드 패턴을 사용해서 변하는 부분과 변하지 않는 부분을 분리해보자.

     


    템플릿 메서드 패턴 - 예제2

     

    템플릿 메서드 패턴 구조 그림

    추상템플릿에 변하지 않는 로직들을 다 몰아 넣는다. 그리고 변하는 부분을 의미하는 메서드를 (위 그림에서는 call()이다.) 만든다. 그리고 call() 메서드는 오버라이딩해서 자식 클래스에서 구현하도록 구성한다. 

     

     

    AbstracTemplate

    @Slf4j
    public abstract class AbstractTemplate {
    
        public void execute(){
            long startTime = System.currentTimeMillis();
            //비지니스 로직 실행
            call();//상속
            //비지니스 로직 종료
            long endTime = System.currentTimeMillis();
            long resultTime = endTime - startTime;
            log.info("resultTime = {}",resultTime);
        }
    
        protected abstract void call();
    }

     

    템플릿 메서드 패턴 이름 그래로 템플릿을 사용하는 방식이다. 템플릿은 기준이 되는 거대한 툴이다. 템플릿이라는 틀에 변하지 않는 부분을 몰아둔다. 그리고 일부 변하는 부분을 호출해서 해결한다.

     

    AbstractTemplate 코드를 보다 변하지 않는 부분인 시간 측정 로직을 몰아둔 것을 확인할 수 있다. 이제 이것이 하나의 템플릿이 된다. 그리고 템플릿 안에서 변하는 부분은 call()메서드를 호출해서 처리한다. 템플릿 메서드 패턴은 부모 클래스에 변하지 않는 템플릿 코드를 준다. 그리고 변하는 부분은 자식 클래스에 두고 상속오버라이딩을 사용해서 처리한다.

     

    SubClassLogic1

    package com.spring.core.trace.template.code;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class SubClassLoic1 extends AbstractTemplate{
        @Override
        protected void call() {
            log.info("비지니스 로직1 실행");
        }
    }
    

     

    변하는 부분인 비지니스 로직1을 처리하는 자식 클래스이다. 템플릿이 호출하는 대상인 call()메서드를 오버라이딩한다.

     

     

    SubClassLogic2

    package com.spring.core.trace.template.code;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j
    public class SubClassLoic2 extends AbstractTemplate{
        @Override
        protected void call() {
            log.info("비지니스 로직2 실행");
        }
    }
    

     

    변하는 부분인 비즈니스 로직2를 처리하는 자식 클래스이다. 템플릿이 호출하는 대상인 call()메서드를 오버라이딩한다.

     

     

    TemplateMethodTest - templateMethodV1() 추가

    /**
         * 템플릿 메서드 패턴 적용
         */
        @Test
        void templateMethodV1(){
            AbstractTemplate template1 = new SubClassLoic1();
            template1.execute();
    
            AbstractTemplate template2 = new SubClassLoic2();
            template2.execute();
    
        }

     

     

    실행결과

     

     

    템플릿 메서드 패턴 이스턴스 호출 그림

    template1.execute()를 호출하면 템플릿 로직인 AbstractTemplate.execute()를 실행한다. 여기서 중간에 call() 메서드를 호출하는데 이 부분이 오버라이딩 되어 있다. 따라서 현재 인스턴스인 subClassc1 인스턴스의 SubClassLogic.call()메서드가 호출된다. 

     

    템플릿 메서드 패턴은 이렇게 다형성을 사용해서 변하는 부분과 변하지 않는 부분을 분리하는 방법이다. 

     


    템플릿 메서드 패턴 - 정의

    GOF 디자인 패턴에서는 템플릿 메서드 패턴을 다음과 같이 정의했다.

    템플릿 메서드 디자인 패턴의 목적은 다음과 같습니다.
    "작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다. 템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있습니다."

     

     

    GOF 템플릿 메서드 패턴 정의

    풀어서 설명하면 다음과 같다.

    부모 클래스에 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는 것이다. 이렇게 하면 자식 클래스가 알고리즘 전체 구조를 변경하지 않고, 특정 부분만 재정의할 수 있다. 결국 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는 것이다.

     

    하지만

    템플릿 메서드 패턴은 상속을 사용한다. 따라서 상속에서 오는 단점들을 그대로 안고있다. 특히 자식 클래스가 부모 클래스와 컴파일 시점에 강하게 결합되는 문제가 있다. 이것은 의존관계에 대한 문제이다. 자식클래스 입장에서는 부모 클래스의 기능을 전혀 사용하지 않는다. 

    @Slf4j
    public class SubClassLoic1 extends AbstractTemplate{
        @Override
        protected void call() {
            log.info("비지니스 로직1 실행");
        }
    }
    

     

    위 코드를 보면 알수 있듯이 SubClassLocic1 클래스는 부모클래스의 모든 것을 알고 있지만 부모클래스의 얻너 기능도 사용하지 않는다. 

     

     

    템플릿 메서드를 사용한 로그 추적기를 만들면서 자식클래스를 작성할 때 부모 클래스의 기능을 사용한 것이 있었던가?

    그럼에도 불구하고 템플릿 메서드 패턴을 위해 자식 클래스는 부모 클래스를 상속 받고 있다.

     

    상속을 받는 다는 것을 특정 부모 클래스를 의존하고 있다는 것이다. 자식 클래스의 extends 다음에 바로 부모 클래스가 코드상에 지정되어 있다. 따라서 부모 클래스의 기능을 사용하느냐 여부와 상관 없이 부모 클래스를 강하게 의존하게 된다. 여기서 의존한다는 뜻은 자식 클래스코드에 부모 클래스의 코드가 명시되어 있다는 의미이다.  UML에서 상속을 받으면 삼각형 화살표가 자식 -> 부모를 향하고 있는 것은 이런 의존 관계를 반영한 것이다. 

     

    자식클래스 입장에서는 부모 클래스의 기능을 전혀 사용하지 않는데, 부모 클래스를 알아야한다. 이것은 좋은 설계가 아니다. 그리고 이런 잘못된 의존관계 때문에 부모 클래스를 수정하면, 자식 클래스에도 영향을 줄 수 있다. 

     

    추가로 템플릿 메서드 패턴은 상속 구조를 상용하기 때문에 별도의 클래스나 익명 내부 클래스를 만들어야 하는 부분도 복잡하다. 앞서 설명한 문제점들을 더 깔끔하게 개선하려면 어떻게 해야할까? →

     

    템플릿 메서드 패턴과 비슷한 역할을 하면서 상속의 단점을 제거할 수 있는 디자인 패턴이 바로 전략 패턴(Strategy Pattern)이다. (상속 보다 위임)

     

     

    [출처 - 스프링 핵심원리-고급편, 저 김영한]

    https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B3%A0%EA%B8%89%ED%8E%B8

     

    스프링 핵심 원리 - 고급편 강의 - 인프런

    스프링의 핵심 원리와 고급 기술들을 깊이있게 학습하고, 스프링을 자신있게 사용할 수 있습니다., 핵심 디자인 패턴, 쓰레드 로컬, 스프링 AOP스프링의 3가지 핵심 고급 개념 이해하기 📢 수강

    www.inflearn.com

     

    댓글

Designed by Tistory.