템플릿 메서드 패턴

템플릿 메서드 패턴이란?

  • 핵심기능: 해당 객체가 제공하는 고유기능
    • 메서드가 제공하는 비즈니스 로직
  • 부가기능: 핵심기능을 보조하기 위한 기능
    • 로깅, 트랜잭션 등 핵심기능을 보조하며 핵심기능과 함께 사용되는 기능
    • 여러 핵심기능에 대해 동일한 부가기능을 제공
    • 부가기능만 단독으로 사용는 경우는 않음
  • 핵심 기능
  public void orderItem(String itemId) {
    orderRepository.save(itemId);
  }
  • 부가 기능(로깅) 추가
  public void orderItem(String itemId) {
    TraceStatus status = null;
    try {
      status = trace.begin("OrderService.orderItem()");
      orderRepository.save(itemId); // 핵심 기능
      trace.end(status)
    } catch (Exception e) {
      trace.exception(status, e);
      throw e;
    }
  }
  • 핵심기능이 많을수록 부가기능에 대한 코드 사용처도 늘어남
  • 중간에 핵심 기능의 코드만 다르고 부가 기능의 코드는 동일함
    • 즉 부가 기능의 코드는 중복코드
  • 핵심 기능만 작성하고 부가 기능 코드는 재사용하도록 작성할 필요가 있음
    • 좋은 설계는 변하는 것과 변하지 않는 것을 분리하는 설계
    • 핵심 기능과 부가 기능을 분리해서 모듈화
  • 이를 해결할수 있는 디자인 패턴이 템플릿 메서드 패턴(Template Method Pattern)

템플릿 메서드 예시

추상 클래스를 사용한 예시

public class AbstractTemplate {
  public void execute() {
    TraceStatus status = null;
    try {
      status = trace.begin("OrderService.orderItem()");
      call();
      trace.end(status)
    } catch (Exception e) {
      trace.exception(status, e);
      throw e;
    }
  }

  protected abstract void call();
}

public class SubClass extends AbstractTemplate {
  @Override
  protected void call() { 
    orderRepository.save(itemId);
  }
}
  • 핵심은 상속기반의 다형성
    • 호출하는 클라이언트는 AbstractTemplate 인스턴스의 call 메서드 호출
  • 부가 기능에 수정이 필요하면 AbstractTemplate만 수정하면 됨
    • 단일 책임 원칙이 잘 지켜진 것

익명 내부 클래스를 사용한 예시

  • 상속 기반의 템플릿 메서드 패턴은 매번 클래스를 정의해야함
    • 익면 내부 클래스를 사용하면 이런 단점을 보완할 수 있음
    • 인스턴스를 생성하면서 동시에 자식 클래스를 정의가능
public class Service {
  void templateMothod() {
    AbstractTemplate template1 = new AbstractTemplate() {
      @Override
      protected void call() { 
        orderRepository.save(itemId);
      }
    }
    template1.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();
}

public class OrderService {
  private final OrderRepository 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()");
    }
  }
}

좋은 설계란?

  • 진정한 좋은 설계란 변경이 일어날 때 자연스럽게 드러남
    • 단일 책임 원칙(SRP)

템플릿 메서드 패턴 - 정의

  • 작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기
  • 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의 가능
  • 상속과 오버라이딩을 통한 다형성으로 문제를 해결
  • 그러나 자식클래스가 부모클래스를 의존함, 특히 컴파일 시점에 강하게 결합됨
    • 자식 클래스는 부모 클래스를 사용하지 않지만 상속을 사용하기 때문
  • 템플릿 메서드 패턴과 비슷하면서 상속의 단점을 제거하는 디자인 패턴이 전략 패턴

References