템플릿 콜백 패턴

아엘·2024년 3월 31일
0

spring framework

목록 보기
5/5

템플릿 콜백 패턴은 GoF 패턴은 아니고, 스프링 내부에서 이런 방식을 자주 사용해서, 스프링안에서만 부른다고 합니다. 스프링에서는 JdbcTemplate, RestTemplate, KafkaTemplate처럼 다양한 템플릿 콜백패턴이 사용됩니다.

콜백 정의는 다음과 같습니다.

프로그래밍에서 콜백 또는 콜애프터 함수는 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말합니다.

자바 언어에서 콜백

  • 자바에서 실행 가능한 코드를 인수로 넘기려면 객체가 필요하다. 자바8부터는 람다를 사용할 수 있습니다.
  • 자바8이전에는 보통 하나의 메서드를 가진 인터페이스를 구현하고, 주로 익명 내부클래스를 사용했습니다.

템플릿 콜백 패턴은 전략패턴과 동일형태입니다. 전략패턴은 템플릿 메서드 패턴을 보완하는 패턴입니다.

그래서 템플릿 메서드 패턴부터 전략 패턴 그리고 템플릿 콜백 패턴까지 자바 코드로 예시를 들어보면서 확인해보겠습니다.

템플릿 메서드 패턴

우리는 코드를 작성할때 굉장히 자주 마주치는 문제가 있습니다.
"변하는 것과 변하지 않는 것을 분리"

뭐가 변하고 뭐가 변하지 않으냐 물은다면, 핵심 기능은 변하고 부가 기능은 변하지 않는다라고 봅니다.
이 둘을 분리해서 모듈화해야하면 테스트 코드 작성도 쉽고, 변화에 유연하게 대응하는 코드를 작성할 수 있습니다.

템플릿 메서드 패턴은 디자인 패턴중 하나입니다.

템플릿 메서드 패턴 구조

템플릿 메서드 패턴이 변하지 않는 것은 Template 추상클래스에서 구성하고, 핵심로직은 각 클래스가 확장해서 사용합니다.
그런데, AbstractTemplate에 변경에 자식 클래스가 코드 변경이 취약해집니다.

코드는 최대한 변경이 적도록 개발해야지 프로젝트 규모가 커져도 유지보수 시간을 줄일 수 있습니다.

코드 예시

@Slf4j
public class ContextV1 {

    private final Strategy strategy;

    public ContextV1(Strategy strategy) {
        this.strategy = strategy;
    }

    public void execute() {
        long startTimeMs = System.currentTimeMillis();

        strategy.call();

        long endTImeMs = System.currentTimeMillis();
        log.info("Execution time: {} ms", endTImeMs - startTimeMs);
    }
}

...
@Slf4j
public class StrategyLogic1 implements Strategy {
    @Override
    public void call() {
        log.info("비즈니스로직1 실행");
    }
}

...
@Slf4j
public class StrategyLogic2 implements Strategy{
    @Override
    public void call() {
        log.info("비즈니스로직2 실행");
    }
}

import org.junit.jupiter.api.Test;

public class ContextV1Test {
    @Test
    void strategyV1() {
        StrategyLogic1 strategyLogic1 = new StrategyLogic1();
        ContextV1 contextV1 = new ContextV1(strategyLogic1);
        contextV1.execute();

        StrategyLogic2 strategyLogic2 = new StrategyLogic2();
        ContextV1 contextV2 = new ContextV1(strategyLogic2);
        contextV2.execute();
    }

    @Test
    void strategyV2() {
        ContextV1 contextV1 = new ContextV1(
                new Strategy() {
                    @Override
                    public void call() {
                        System.out.println("비즈니스로직1 실행");
                    }
                }
        );
        contextV1.execute();

        ContextV1 contextV2 = new ContextV1(
                () -> System.out.println("비즈니스로직2 실행")
        );

        contextV2.execute();
    }
}

전략 패턴

전략패턴은 템플릿메서드 패턴의 보완을 할 수 있습니다.
Context는 변하지 않은 템플릿 Strategy는 변하는 핵심 로직 역할을 다룹니다.

Context는 변하지 않지만, strategy를 통해 일부 전략이 변경됩니다.
Context코드가 변경되도 strategy에는 전혀 영향받지 않아요.

확장보다는 위임이 변화에 유연한 설계가 되었습니다.

익명 내부 클래스를 java8부터 제공하는 람다로 변경할 수 있습니다. 람다로 변경하려면 인터페이스에 메서드가 1개만 있으면 되는데, 여기에서 제공하는 Strategy 인터페이스 메서드가 1개만 있으므로 람다로 사용할 수 있습니다.

애플리케이션 로딩시점에서 의존관계를 모두 매핑하기 때문에 전략변경 하기가 어려운 구조이다.

Context를 실행할때마다 전략을 인수로 전달하는 방식으로 하면 실행 시점에 원하는 전략에 따른 핵심로직이 동작할 수 있어, 보다 유연한 설계가 되었다.

인자로 strategy를 전달하는 방식도, 실행할때마다 전략을 지정해줘야 한다는 단점이 있습니다.

코드 예시

@Slf4j
public class ContextV2 {

    public void execute(Strategy strategy) {
        long startTimeMs = System.currentTimeMillis();

        strategy.call();

        long endTImeMs = System.currentTimeMillis();
        log.info("Execution time: {} ms", endTImeMs - startTimeMs);
    }
}
@Slf4j
public class ContextV2Test {

    @Test
    void strategyV2() {
        ContextV2 contextV2 = new ContextV2();
        contextV2.execute(  new Strategy() {
            @Override
            public void call() {
                log.info("비즈니스로직1 실행");
            }
        });

        contextV2.execute(() -> log.info("비즈니스로직2 실행"));
    }
}

템플릿 콜백 패턴

템플릿 콜백 패턴은
전략패턴에서 인자로 실행가능한 코드를 전달했던 패턴과 동일합니다.
이름만 ContextV2 -> Template
Strategy -> Callback으로 치환하면 됩니다.

@Slf4j
public class TimeLogTemplate {

    public void execute(Callback callback) {
        long startTimeMs = System.currentTimeMillis();

        callback.call();

        long endTImeMs = System.currentTimeMillis();
        log.info("Execution time: {} ms", endTImeMs - startTimeMs);
    }
}
@Slf4j
public class TemplateCallbackTest {

    @Test
    void callbackV1() {
        TimeLogTemplate template = new TimeLogTemplate();
        template.execute(new Callback() {
            @Override
            public void call() {
                log.info("비즈니스로직1 실행");
            }
        });

        template.execute(() -> log.info("비즈니스로직2 실행"));
    }
}

출처: 스프링 핵심 원리 - 고급편

profile
하루 하나씩

0개의 댓글