ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 템플릿 메서드 패턴
    스프링/스프링 AOP 2023. 9. 3. 23:38
    @GetMapping("v3/request")
    public String request(String itemId) {
    
        TraceStatus status = null;
    
        try {
            status = trace.begin("OrderController.request()");
            orderService.orderItem(itemId); // 핵심 로직
            trace.end(status);
            return "ok";
        } catch (Exception e) {
            trace.exception(status, e);
            throw e;
        }
    }

    원래는 로직이 orderService.orderItem(itemId) 하나뿐인데 로그 추적기를 위해서 코드가 너무 복잡해졌다. 또한, Service와 Repository 계층에서도 같은 코드가 계속해서 반복된다.

     

     

    템플릿 메서드 패턴은 핵심 기능과 부가 기능(로그 추적기)를 분리하는 디자인 패턴이다.

     

    템플릿 메서드 패턴은 이름 그대로 템플릿을 사용하는 방식인데, 변하지 않는 부분을 모아둔 다음에 일부의 변하는 부분을 별도로 호출해서 해결하는 패턴이다.

     

    @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이라는 추상 클래스를 만들었다. 반복되는 변하지 않는 로직들을 execute()에 넣은 다음에 call() 메서드를 통해서 각각 변하는 부분의 로직을 넣어주면 된다.

     

    템플릿 메서드 패턴은 부모 클래스에 변하지 않는 템플릿 코드를 넣어주고 변하는 부분은 자식 클래스에서 상속과 오버라이딩을 사용해서 처리한다.

     

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

    오버라이딩된 call()메서드를 구현하면 각각 필요한 메서드가 execute()에 들어가서 실행된다.

     

    @Test
    void templateMethodV1() {
        AbstractTemplate template1 = new SubClassLogic1();
        template1.execute();
    
        AbstractTemplate template2 = new SubClassLogic2();
        template2.execute();
    }

    코드의 중복이 없이 SubClassLogic 구현체를 통해서 비즈니스 로직을 설정한 다음 template.execute()로 동일한 부분을 호출할 수 있다.

     

     

     

    익명 내부 클래스 활용

    @Test
    void templateMethodV2() {
        AbstractTemplate template1 = new AbstractTemplate() {
    
            @Override
            protected void call() {
                log.info("비즈니스 로직1 실행");
            }
        };
        template1.execute();
    
        AbstractTemplate template2 = new AbstractTemplate() {
    
            @Override
            protected void call() {
                log.info("비즈니스 로직2 실행");
            }
        };
        template2.execute();
    }

    별도로 구현 클래스를 만들지 않고 익명 내부 클래스로 만들어서 사용할 수도 있다.

     

     

     

     

    템플릿 패턴의 실제 적용

     

    테스트 코드가 아니라 실제 웹에서의 템플릿 패턴을 적용한 모습이다.

     

    AbstractTemplate이라는 추상 클래스를 만들어준다.

    public abstract class AbstractTemplate<T> {
    
        private final LogTrace trace;
    
        public AbstractTemplate(LogTrace trace) {
            this.trace = trace;
        }
    
        public T execute(String message) {
            TraceStatus status = null;
            try {
    
                status = trace.begin(message);
    
                // 로직 호출
                T result = call();
    
                trace.end(status);
                return result;
            } catch (Exception e) {
                trace.exception(status, e);
                throw e; // 예외를 꼭 다시 던져주어야 한다.
    
            }
        }
    
        protected abstract T call();
    }

     

     

    Controller

    @RestController
    @RequiredArgsConstructor
    public class OrderControllerV4 {
    
        private final OrderServiceV4 orderService;
        private final LogTrace trace;
    
        @GetMapping("v4/request")
        public String request(String itemId) {
    
            AbstractTemplate<String> template = new AbstractTemplate<String>(trace) {
                @Override
                protected String call() {
                    orderService.orderItem(itemId);
                    return "ok";
                }
            };
            return template.execute("OrderController.request(");
        }
    }

    컨트롤러만 봐도 코드가 훨씬 단순해진 것을 확인할 수 있다. 익명 내부클래스를 제외하면 한줄뿐이다.

    Service와 Repository에도 동일하게 코드를 작성하면 된다.

Designed by Tistory.