ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 템플릿 메서드 패턴 & 전략 패턴 & 템플릿 콜백 패턴
    Spring&SpringBoot 2025. 10. 13. 18:06

    템플릿 메서드 패턴

    변하는 부분(세부 동작)은 하위 클래스에 구현하고 변하지 않는 부분은 상위 클래스에서 정의하도록 하는 패턴

    하지만 상속에서 오는 단점들이 그대로 존재하며 특히 자식 클래스가 부모 클래스와 컴파일 시점에 강하게 결합되는 문제가 있다

    자식 클래스 입장에서는 부모 클래스의 기능을 전혀 사용하지 않는데 부모 클래스를 알아야 한다

    이런 잘못된 의존관계 때문에 부모 클래스를 수정하면 자식 클래스에도 영향을 줄 수 있다 

     

     

    @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();
    }
    • 템플릿은 기준이 되는 거대한 틀
    • 템플릿이라는 틀에 변하지 않는 부분을 몰아 두고 일부 변하는 부분을 별도로 호출해서 해결한다

     

     

    @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 실행");
        }
    }
    • 변하는 부분인 비즈니스 로직1 또는 로직2를 처리하는 자식 클래스

     

     

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

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

     

     

    /**
     * 템플릿 메서드 패턴, 익명 내부 클래스 사용
     */
    @Test
    void templateMethodV2() {
        AbstractTemplate template1 = new AbstractTemplate() {
            @Override
            protected void call() {
                log.info("비즈니스 로직1 실행");
            }
        };
        log.info("클래스 이름1={}", template1.getClass());
        template1.execute();
        
        AbstractTemplate template2 = new AbstractTemplate() {
            @Override
            protected void call() {
                log.info("비즈니스 로직1 실행");
            }
        };
        log.info("클래스 이름2={}", template2.getClass());
        template2.execute();
    }

     

     

    템플릿 메서드 패턴 적용

    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();
        }
    }

     

     @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<>(trace) {
                   @Override
                   protected String call() {
                        orderService.orderItem(itemId);
                        return "ok";
                   }
              };
              return template.execute("OrderController.request()");
         }
    }
    @Service
    @RequiredArgsConstructor
    public class OrderServiceV4 {
        private final OrderRepositoryV4 orderRepository;
        private final LogTrace trace;
    
        public void orderItem(String itemId) {
            AbstractTemplate<Void> template = new AbstractTemplate<>(trace) {
                @Override
                protected Void call() {
                    orderRepository.save(itemId);
                    return null;
                }
            };
            template.execute("OrderService.orderItem()");
        }
    }
    @Repository
    @RequiredArgsConstructor
    public class OrderRepositoryV4 {
        private final LogTrace trace;
        
        public void save(String itemId) {
            AbstractTemplate<Void> template = new AbstractTemplate<>(trace) {
                @Override
                protected Void call() {
                    // 저장 로직
                    if (itemId.equals("ex")) {
                        throw new IllegalStateException("예외 발생!");
                    }
                    sleep(1000);
                    return null;
                }
            };
            template.execute("OrderRepository.save()");
        }
    
        private void sleep(int millis) {
            try {
                Thread.sleep(millis);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    템플릿 메서드 패턴과 비슷한 역할을 하면서 상속의 단점을 제거할 수 있는 디자인 패턴이 전략 패턴이다 

     

     

    전략 패턴

    변하지 않는 코드(문맥, Context)와 자주 바뀌는 코드(전략, Strategy)를 분리해서 실행 시점에 전략을 바꿔 끼울 수 있게 만드는 디자인 패턴

     

     

    public interface Strategy {
        // 인터페이스는 변하는 알고리즘 역할
        void call();
    }
    @Slf4j
    public class StrategyLogic1 implements Strategy {
        @Override
        public void call() {
            // 변하는 알고리즘은 Strategy 인터페이스를 구현
            log.info("비즈니스 로직1 실행");
        }
    }
    /**
     * 필드에 전략을 보관하는 방식
     */
    @Slf4j
    public class ContextV1 {
        // ContextV1은 변하지 않는 로직을 가지고 있는 템플릿 역할
        private Strategy strategy;
        
        public ContextV1(Strategy strategy) {
            this.strategy = strategy;
        }
        
        public void execute() {
            long startTime = System.currentTimeMillis();
            // 비즈니스 로직 실행
            strategy.call(); //위임
            // 비즈니스 로직 종료
            long endTime = System.currentTimeMillis();
            long resultTime = endTime - startTime;
            log.info("resultTime={}", resultTime);
        }
    }
    /**
     * 전략 패턴 적용
    */
    @Test
    void strategyV1() {
        Strategy strategyLogic1 = new StrategyLogic1();
        ContextV1 context1 = new ContextV1(strategyLogic1);
        context1.execute();
        
        Strategy strategyLogic2 = new StrategyLogic2();
        ContextV1 context2 = new ContextV1(strategyLogic2);
        context2.execute();
    }
    
    /**
     * 전략 패턴, 람다
    */
    @Test
    void strategyV4() {
        ContextV1 context1 = new ContextV1(() -> log.info("비즈니스 로직1 실행"));
        context1.execute();
        ContextV1 context2 = new ContextV1(() -> log.info("비즈니스 로직2 실행"));
        context2.execute();
    }

    • 선조립 후 실행
      • context와 strategy를 실행 전에 원하는 모양으로 조립해 두고 context를 실행하는 선 조립 후 실행 방식에서 매우 유용
      • 단점은 조립 후 전략을 변경하기 번거롭다

     

     

    /**
    * 전략을 파라미터로 전달 받는 방식
    */
    @Slf4j
    public class ContextV2 {
        public void execute(Strategy strategy) {
            long startTime = System.currentTimeMillis();
            // 비즈니스 로직 실행
            strategy.call(); //위임
            // 비즈니스 로직 종료
            long endTime = System.currentTimeMillis();
            long resultTime = endTime - startTime;
            log.info("resultTime={}", resultTime);
        }
    }
    @Slf4j
    public class ContextV2Test {
        @Test
        void strategyV1() {
            ContextV2 context = new ContextV2();
            context.execute(new StrategyLogic1());
            context.execute(new StrategyLogic2());
        }
        
        /**
        * 전략 패턴 익명 내부 클래스
        */
        @Test
        void strategyV2() {
            ContextV2 context = new ContextV2();
            context.execute(new Strategy() {
                @Override
                public void call() {
                    log.info("비즈니스 로직1 실행");
                }
            });
            
            context.execute(new Strategy() {
                @Override
                public void call() {
                    log.info("비즈니스 로직2 실행");
                }
            });
        }
        
        /**
        * 전략 패턴 익명 내부 클래스2, 람다
        */
        @Test
        void strategyV3() {
            ContextV2 context = new ContextV2();
            context.execute(() -> log.info("비즈니스 로직1 실행"));
            context.execute(() -> log.info("비즈니스 로직2 실행"));
        }
    }
    • 전략을 더욱 유연하게 변경할 수 있다

     

     

    템플릿 콜백 패턴

    전략 패턴의 변형으로, 전략을 “객체” 대신 “콜백 함수(익명 클래스, 람다)”로 전달하는 방식

    전략 패턴에서 Context가 템플릿 역할을 하고 Strategy 부분이 콜백으로 넘어온다

    스프링에서는 JdbcTemplate, RestTemplate, RedisTemplate처럼 다양한 템플릿 콜백 패턴이 사용된다

    public interface Callback {
        void call();
    }
    @Slf4j
    public class TimeLogTemplate {
        public void execute(Callback callback) {
            long startTime = System.currentTimeMillis();
            // 비즈니스 로직 실행
            callback.call(); //위임
            // 비즈니스 로직 종료
            long endTime = System.currentTimeMillis();
            long resultTime = endTime - startTime;
            log.info("resultTime={}", resultTime);
        }
    }
    @Slf4j
    public class TemplateCallbackTest {
        // 템플릿 콜백 패턴 - 익명 내부 클래스
        @Test
        void callbackV1() {
            TimeLogTemplate template = new TimeLogTemplate();
            template.execute(new Callback() {
                @Override
                public void call() {
                    log.info("비즈니스 로직1 실행");
                }
            });
    
            template.execute(new Callback() {
                @Override
                public void call() {
                    log.info("비즈니스 로직2 실행");
                }
            });
        }
    
        // 템플릿 콜백 패턴 - 람다
        @Test
        void callbackV2() {
            TimeLogTemplate template = new TimeLogTemplate();
            template.execute(() -> log.info("비즈니스 로직1 실행"));
            template.execute(() -> log.info("비즈니스 로직2 실행"));
        }
    }
    • 별도의 클래스를 만들어서 전달해도 되지만, 콜백을 사용할 경우 익명 내부 클래스나 람다를 사용하는 것이 편리하다

    728x90

    'Spring&SpringBoot' 카테고리의 다른 글

    스프링 AOP 실무 주의사항  (0) 2025.10.21
    로그출력AOP & 재시도AOP  (0) 2025.10.21
    로그추적기 - 동시성 문제  (0) 2025.10.02
    스프링 퀵 스타트 04  (0) 2025.09.22
    스프링 퀵 스타트 03  (0) 2025.09.18
Designed by Tistory.