본문으로 건너뛰기

1. 템플릿 메서드 패턴 개요

  • 템플릿 메서드 패턴은 객체지향 설계의 핵심 원칙인 "변하는 것과 변하지 않는 것을 분리"하는 것을 실현하는 고전적인 디자인 패턴입니다.

1.1 핵심 개념

  • 알고리즘의 골격을 부모 클래스에 정의합니다.
  • 변경이 필요한 부분만 자식 클래스에서 구현합니다.
  • 전체 알고리즘 구조는 유지하면서 특정 단계만 변경 가능합니다.
정보

GOF의 정의: "작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기합니다. 템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할 수 있습니다."

1.2 패턴의 구조

  • 추상 클래스: 알고리즘의 뼈대 정의
  • 구체 클래스: 실제 비즈니스 로직 구현
  • 훅(Hook) 메서드: 선택적으로 오버라이딩 가능한 메서드

2. 템플릿 메서드 패턴 구현

2.1 추상 클래스 정의

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는 템플릿 메서드인 execute()를 정의합니다.
  • execute() 메서드는 알고리즘의 골격을 정의하고, call() 메서드를 호출합니다.
  • call() 메서드는 추상 메서드로, 자식 클래스에서 반드시 구현해야 합니다.
    • 이 메서드는 구체 클래스에서 실제 비즈니스 로직을 구현합니다.
    • 구체 클래스마다 서로 다른 로직을 수행할 수 있습니다.

2.2 구체 클래스 구현

@Slf4j
public class SubClassLogic1 extends AbstractTemplate {
@Override
protected void call() {
log.info("비즈니스 로직1 실행");
}
}
  • 구체 클래스는 추상 클래스를 상속받아 call() 메서드를 구현합니다.
  • call() 메서드는 구체 클래스마다 다른 비즈니스 로직을 수행할 수 있습니다.
  • 템플릿 메서드 패턴을 통해 알고리즘의 구조는 유지하면서 특정 단계만 변경할 수 있습니다.

3. 패턴 활용하기

3.1 일반적인 상속 방식

@Slf4j
public class OrderService extends AbstractTemplate<String> {
@Override
protected String call() {
// 구체적인 비즈니스 로직 구현
return "order processed";
}
}
  • 위 코드는 가장 일반적인 상속 방식으로 템플릿 메서드 패턴을 활용합니다.
  • OrderService는 AbstractTemplate을 상속받아 call() 메서드를 구현합니다.

3.2 익명 클래스 활용

@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
private final LogTrace trace;

@GetMapping("/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()");
}
}
  • 익명 클래스를 활용하면 템플릿 메서드 패턴을 더 유연하게 활용할 수 있습니다.
  • OrderController에서는 AbstractTemplate을 익명 클래스로 구현하여 사용합니다.

4. 장단점 분석

4.1 장점

  • 코드 중복 제거
  • 알고리즘의 구조 유지
  • 유지보수성 향상
  • 확장성 확보

4.2 한계와 주의점

경고

템플릿 메서드 패턴은 상속을 사용하기 때문에 다음과 같은 한계가 있습니다:

  • 컴파일 시점 의존 관계로 인한 강한 결합
  • 부모 클래스 수정 시 자식 클래스 영향
  • 상속의 단점을 그대로 안고 감

5. 대안 패턴

5.1 전략 패턴

  • 상속 대신 위임을 사용합니다.
  • 더 유연한 구조 제공합니다.
  • 런타임에 알고리즘 변경이 가능합니다.
  • Strategy 참고

5.2 템플릿 콜백 패턴

  • 전략 패턴의 확장으로, 콜백을 활용합니다.
  • 전략 패턴은 생성 시점에 전략을 결정하지만, 템플릿 콜백 패턴은 실행 시점에 전략을 결정합니다.
  • 따라서 보다 동적인 전략 변경이 가능합니다.
  • Template Callback 참고

상속으로 인한 제약이 부담스러운 경우, 전략 패턴을 고려해 보세요. 전략 패턴은 템플릿 메서드 패턴과 비슷한 역할을 하면서도 상속의 단점을 제거할 수 있습니다.

6. 실전 적용 가이드

6.1 적용 시나리오

  • 알고리즘의 구조가 안정적일 때
  • 변경이 필요한 부분이 명확할 때
  • 코드 재사용이 주요 관심사일 때

6.2 구현 팁

  • 추상 메서드는 필요한 만큼만 정의
  • 훅 메서드를 적절히 활용
  • 명확한 네이밍 규칙 적용
  • 문서화를 통한 의도 명확히 전달

7. 마치며

  • 템플릿 메서드 패턴은 알고리즘의 구조를 고정하면서 특정 단계만을 유연하게 변경할 수 있게 해주는 강력한 도구입니다.
  • 하지만 상속이라는 제약이 있으므로, 사용 시 이러한 제약과 대안을 충분히 고려해야 합니다.

참고 자료: