Design Pattern

Template Callback Pattern

cwchoiit 2023. 12. 12. 16:54
728x90
반응형
SMALL
728x90
SMALL

 

템플릿 콜백 패턴은 굉장히 자주 사용되는 패턴이다. 콜백(또는 콜 애프터 함수라고도 함)은 다른 코드의 인수로서 넘겨주는 실행 가능한 코드를 말한다. 좀 더 직관적으로 말하면 파라미터로 실행 가능한 코드를 넘겨주면 받는 쪽에서 그 코드를 실행하는 것.

 

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

 

이 패턴의 예제를 살펴보자.

 

템플릿 콜백 패턴 예제

Callback.java

package com.example.advanced.trace.strategy.code.template;

public interface Callback {
    void call();
}

 

콜백 로직을 전달할 인터페이스.

 

TimeLogTemplate.java

package com.example.advanced.trace.strategy.code.template;

import lombok.extern.slf4j.Slf4j;

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

 

'execute()'는 파라미터로 Callback 객체를 받는다. (자바는 실행 가능한 코드를 인수로 넘기려면 객체를 전달해야 한다 했다)

받은 객체가 가지고 있는 (정확히는 구현한) 'call()'을 실행한다.

 

TemplateCallbackTest.java

package com.example.advanced.trace.strategy;

import com.example.advanced.trace.strategy.code.template.Callback;
import com.example.advanced.trace.strategy.code.template.TimeLogTemplate;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

@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 실행"));
    }
}

 

템플릿 콜백 패턴을 테스트할 코드다. 콜백함수를 실행할 TimeLogTemplate 객체를 만든다. 이 객체가 가지고 있는 'execute()'는 파라미터로 Callback 객체를 받는다. Callback 객체를 전달하기 위해 익명 내부 클래스로 전달하거나, 람다로 전달하는 방식 두가지 예제 코드가 있다. 물론 별도의 클래스를 따로 만들어서 전달해도 무방하다. 재사용성이 빈번한 경우 그게 더 효율적이다. 그러나 그게 아니라면 위와 같이 람다를 사용하는 것이 좀 더 편리할 것 같다.

 

 

예제 코드 실행 결과

16:44:54.855 [Test worker] INFO com.example.advanced.trace.strategy.TemplateCallbackTest -- 비즈니스 로직1 실행
16:44:54.862 [Test worker] INFO com.example.advanced.trace.strategy.code.template.TimeLogTemplate -- resultTime=9
16:44:54.865 [Test worker] INFO com.example.advanced.trace.strategy.TemplateCallbackTest -- 비즈니스 로직2 실행
16:44:54.865 [Test worker] INFO com.example.advanced.trace.strategy.code.template.TimeLogTemplate -- resultTime=0

 

 

이제 이 템플릿 콜백 패턴을 실사용 코드에 적용한 모습을 보자.

 

 

TraceCallback.java

package com.example.advanced.trace.callback;

public interface TraceCallback<T> {
    T call();
}

 

콜백 로직을 전달할 인터페이스다. 이 때 로직의 반환 타입을 제네릭으로 설정했다. 

 

TraceTemplate.java

package com.example.advanced.trace.callback;

import com.example.advanced.trace.TraceStatus;
import com.example.advanced.trace.logtrace.LogTrace;

public class TraceTemplate {

    private final LogTrace logTrace;

    public TraceTemplate(LogTrace logTrace) {
        this.logTrace = logTrace;
    }

    public <T> T execute(String message, TraceCallback<T> callback) {
        TraceStatus status = null;

        try {
            status = logTrace.begin(message);

            T result = callback.call();

            logTrace.end(status);

            return result;
        } catch (Exception e) {
            logTrace.exception(status, e);
            throw e;
        }
    }
}

 

콜백 함수를 받아 실행할 템플릿 클래스이다. 콜백 함수를 받는 'execute()'는 공통 부분인 메서드의 시작 시간과 종료 시간을 확인하는 코드가 있고 그 사이에 콜백 함수를 실행한다. 콜백 함수의 반환값이 'execute()'의 반환값이 되고 그 반환값이 제네릭이므로 메서드 또한 제네릭이다.

 

이 템플릿을 실제로 사용하는 클라이언트 코드를 살펴보자.

 

OrderControllerV5.java

package com.example.advanced.app.v5;

import com.example.advanced.trace.callback.TraceCallback;
import com.example.advanced.trace.callback.TraceTemplate;
import com.example.advanced.trace.logtrace.LogTrace;
import com.example.advanced.trace.template.AbstractTemplate;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class OrderControllerV5 {

    private final OrderServiceV5 orderService;
    private final TraceTemplate traceTemplate;

    public OrderControllerV5(OrderServiceV5 orderService, LogTrace trace) {
        this.orderService = orderService;
        this.traceTemplate = new TraceTemplate(trace);
    }

    @GetMapping("/v5/request")
    public String request(String itemId) {

        return this.traceTemplate.execute("OrderController.request()", () -> {
            orderService.orderItem(itemId);
            return "ok";
        });
    }
}

 

람다를 사용해 콜백 함수를 넘길 객체를 생성한다. 인터페이스에 메서드가 한 개만 있으면 람다를 사용할 수 있기 때문에 람다 표현식으로 코드를 간결하게 해결했다. 저 내부 코드가 인터페이스의 메서드 'call()'의 구현 메서드이다. 그리고 반환 타입은 제네릭인데 문자열을 반환하므로 String으로 전달된다.

 

 

결론

이런 Template Callback Pattern은 스프링에서도 굉장히 많이 사용되는 패턴이다. (JdbcTemplate, RestTemplate, RedisTemplate,...) 그래서 이러한 구조로 사용되는구나를 알고 보면 좋을듯하다.

 

 

728x90
반응형
LIST

'Design Pattern' 카테고리의 다른 글

Template Method Pattern  (2) 2023.12.12