제육's 휘발성 코딩
article thumbnail
반응형

템플릿 메서드 패턴

템플릿 메서드 패턴은 핵심 기능과, 부가 기능을 분리하여 모듈화하는 디자인 패턴이다.

예제를 통해 템플리 메서드 패턴을 살펴보자.

이전 장에서 로그 추적기를 만들었다. 도입 전과 후의 코드를 비교해보자.

image

  • 도입 전에는 핵심 기능만 남아 있는 반면, 도입 후에는 부가 기능이 훨씬 많고 복잡하다.

핵심 기능 vs 부가 기능

  • 핵심 기능은 객체가 제공하는 고유의 기능이다. orderService주문이 핵심 기능이다.
  • 부가 기능은 핵심 기능을 보조하기 위해 제공하는 기능이다. 예를 들면 로그, 트랜잭션 등을 들 수 있다.

TemplateMethodTest

@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("비즈니스 로직 2 실행");
      // 비즈니스 로직 종료
      long endTime = System.currentTimeMillis();
      long resultTime = endTime - startTime;
      log.info("resultTime={}", resultTime);
  }
}
  • 다음 코드를 살펴보면, logic1()logic2() 는 시간을 측정하는 부분과 비즈니스 로직을 실행하는 부분이 함께 존재한다.
  • 이 코드를 템플릿 메서드 패턴에 맞게 분리하여 구현해보자.

 

템플릿 메서드 패턴 - 예제 1

템플릿 메서드 패턴은 이름 그대로 템플릿을 사용하는 것이다.

변하지 않는 부분은 몰아서 구현하고, 변하는 부분을 별도로 호출해서 해결한다.

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

  protected abstract void call();
}
  • 시간 측정 로직은 변하지 않는다. 따라서 하나의 템플릿으로 만들어둔다.
  • 템플릿 안에서 변하는 비즈니스 로직을 자식이 상속 받아 구현하도록 처리한다.

SubClass

@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 실행");
    }
}
  • 변하는 부분인 call()을 자식이 상속 받아 구현하도록 처리했다.

 

TemplateMethodTest - templateMethodV1() 테스트

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

    AbstractTemplate template2 = new SubClassLogic2();
    template2.execute();
}
  • 템플릿 메서드 패턴으로 구현한 코드를 실행해보자.

image

  • 다음과 같이 구현체 별로 실행 시간을 측정하는 로직이 정상 실행된다.

 

템플릿 메서드 패턴 - 예제 2

예제 1을 살펴보면 그럴싸하게 템플릿 메서드를 적용한 것 같다.

하지만 필요할 때 마다 구현체 클래스를 만드는 것은 큰 단점으로 작용할 수 있다.

익명 내부 클래스 사용하기

익명 내부 클래스를 사용하면 클래스 생성에 단점을 보완할 수 있다.

참고로 익명 내부 클래스란, 이름이 없고 클래스 내부에 선언 되는 클래스를 의미한다.

TemplateMethodTest - templateMethodV2() 추가

  @Test
  void templateMethod2() {
      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("비즈니스 로직2 실행");
          }
      };
      log.info("클래스 이름2={}", template2.getClass());
      template2.execute();
  }
  • 익명 내부 클래스를 사용해서도 동일한 결과를 얻을 수 있다.
  • 익명 내부 클래스는 getClass() 를 했을 때 이름이 없기 때문에 자바에서 임의로 만든 TemplateMethodTest$1 과 같이 출력 된다.

 

템플릿 메서드 패턴 - 적용 1

이전에 만든 로그 추적기에 템플릿 메서드 패턴을 적용해보자.

AbstractTemplate

@Slf4j
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(); // 자식이 상속 받아 구현할 것
}
  • AbstractTemplate은 템플릿 메서드 패턴에서 부모 클래스이고, 템플릿 역할을 하고 있다.
  • <T> 제네릭을 사용하여 타입 세이프티하도록 변경하였다. 참고로 반환타입이 없는 경우 java.lang.Void 타입을 쓰면 된다.

 

OrderControllerV4 변경 전 후

image

  • 변경한 템플릿 메서드 덕에 코드가 간결해졌다.

 

OrderServiceV4 변경 전 후

image

 

OrderRepositoryV4 변경 전 후

image

 

좋은 설계란

템플릿 메서드 패턴을 적용하여 변하는 코드와 변하지 않는 코드를 명확하게 분리했다.

좋은 설계란 여러 정의가 있지만, 변경이 일어날 때 간단하게 처리되는 것이 좋은 설계의 대표적인 예로 볼 수 있다.

GOF 디자인 패턴에는 템플릿 메서드를 다음과 같이 정의했다.

 

 

"작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기한다.

템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있다."

 


본 포스팅은 [인프런] - 스프링 핵심 원리 고급편을 참고하여 포스팅하였습니다.

반응형
profile

제육's 휘발성 코딩

@sasca37

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요! 맞구독은 언제나 환영입니다^^