Spring 핵심원리 고급편 - Template Method 패턴

목차

Template Method 패턴

상속을 통해 슈퍼클래스의 기능을 확장할 때 사용하는 가장 대표적인 방법. 변하지 않는 기능은 슈퍼클래스 에 만들어두고 자주 변경되며 확장할 기능은 서브클래스 에서 만들도록 한다.(토비의 스프링)

  • 변하는 부분변하지 않는 부분 을 분리한다.
  • 템플릿은 기준이 되는 거대한 틀이다. 템플릿 안에 변하지 않는 부분을 넣고, 일부 변하는 부분을 별도로 호출한다.

구조가 동일하고 중간에 핵심기능을 사용하는 코드만 다르다.

Template Method 패턴 적용 전

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

Template Method 패턴 적용 후

로그를 찍은 공통적인 로직 을 묶어 하나의 Template 을 만들어준다. 추상화된 Method 를 Template 내에 넣어줌으로써 하위 객체에서 각각의 비즈니스 로직을 구현하도록 한다. 이를 통해 비즈니스 로직 실행시 시작 시간과 종료시간에 대한 로그를 찍고 싶은 객체는 AbstractTemplate 객체를 상속해 구현하면 된다.

  • 상속 을 통해 추상 클래스에서 구현된 공통 로직을 하위 클래스에 그대로 적용할 수 있다.
  • 중복된 코드 작성 을 줄일 수 있다.
@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);
}

// Overriding 을 통해 하위 클래스에서 call 메소드를 구현하도록 한다.
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 실행");
}
}

실행

@Test
void templateMethodV1(){
AbstractTemplate template1 = new SubclassLogic1();
template1.execute();

AbstractTemplate template2 = new SubclassLogic1();
template2.execute();
}
02:25:23.488 [Test worker] INFO com.example.advancedspring.trace.template.TemplateMethodTest - 비즈니스 로직 1 실행
02:25:23.491 [Test worker] INFO com.example.advancedspring.trace.template.code.AbstractTemplate - resultTime = 5
02:25:23.493 [Test worker] INFO com.example.advancedspring.trace.template.TemplateMethodTest - 비즈니스 로직 2 실행
02:25:23.493 [Test worker] INFO com.example.advancedspring.trace.template.code.AbstractTemplate - resultTime = 0

익명 내부 클래스 사용하기

템플릿 메서드 패턴은 클래스를 계속 만들어 줘야 하는 단점이 있다.
익명 내부 클래스를 사용하면 해당 단점을 보완할 수 있다.

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

AbstractTemplate template2 = new AbstractTemplate() {
@Override
protected void call() {
log.info("비즈니스 로직 2 실행");
}
};
template2.execute();
}
02:25:23.488 [Test worker] INFO com.example.advancedspring.trace.template.TemplateMethodTest - 비즈니스 로직 1 실행
02:25:23.491 [Test worker] INFO com.example.advancedspring.trace.template.code.AbstractTemplate - resultTime = 5
02:25:23.493 [Test worker] INFO com.example.advancedspring.trace.template.TemplateMethodTest - 비즈니스 로직 2 실행
02:25:23.493 [Test worker] INFO com.example.advancedspring.trace.template.code.AbstractTemplate - resultTime = 0

Spring 핵심원리 고급편 - Template Method Pattern 적용

public abstract class AbstractTemplate<T> {

private final LogTrace trace;

public AbstractTemplate(LogTrace logTrace){
this.trace = logTrace;
}

public T execute(String message){
TraceStatus status = null;
try{
status = trace.begin(message);

// 로직 호출
T result = call();

trace.end(status);

return result;
}catch (Exception e){

}
}

protected abstract T call();
}
@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){
TraceStatus status = null;


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){

TraceStatus status = null;

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