
안녕하세요 우기의 정신차린 취준 일기입니다.
이번에는 템플릿 메서드 패턴에 대해서 설명해볼것입니다!!! 지난 시간에 설명해드린 로그추적기와 같이 소개해드리겠습니다.
❤. 템플릿 메서드 패턴이란?
❤. 결론과 회고
❤. 템플릿 메서드 패턴이란?
🧡.템플리 메서드 패턴의 부재 ?
💛. 배보다 배꼽이 더 큰 보조로직
지난 글의 연장선이라고 말할수있지만 로그 추적기에대한 내가 가지고있던 불만사항이 하나 있었다. 비지니스 로직안에 핵심이되는 로직을 호출하는 행동 이외에 로그를 생성하여 보여주는 로직이 너무 규모가 커서 글에 대한 가독성, 번잡함의 문제를 제시했었다.
두 개의 Controller단을 유심히 살펴보자.


배보다 배꼽이 더 큰상황이지만 우리는 로그 남기는 작업을 버릴순 없다.
이런 문제를 해결하기위해서는 핵심로직(서비스 호출)과 보조로직(로그 생성후 보여줌)을 나누어 모듈화를 해야하는 방법이다. 즉, 좋은설계를 하기위해서는 변하는것(서비스 호출)과 변하지않는것(로그 생성후 보여줌)을 분리하여 모듈화를 진행하는것이다.
💛 핵심로직과 보조로직 ?
핵심 로직 : 해당 객체가 제공하는 고유의 기능이다. OrderService의 핵심기능은 주문하는 로직이라 말할수있다.
보조 로직 : 핵심 기능을 보조하기 위해 제공되는 기능이다. 예를 들어 로그 추적기, 트랜잭션의 행위로 들수있다.
현재 보조 로직은 규모가 너무 큰 상황이다. Service, Controller, Repository에 중복된 보조 로직을 따로 빼낼수 없을까란 생각이든다
하지만 try-catch는 물론이고 핵심기능이 중간에 존재하여 단순하게 메서드 추출은 불가능하다.
🧡.템플릿 메서드 패턴의 등장
템플릿 메서드 패턴은 이러한 문제들을 해결하는 패턴이라고 말할수있다.
쉽게 말해 템플릿이라는 것 자체는 JSP, timeleaf 이런것들이 템플릿이라 말할수있다. 기본적인 틀 안에서 조금 내용 바꾸는걸 템플릿이라는것이다.
즉, 어떠한 기준이되는 거대한틀, 그 틀 안에서 변하지 않는 부분들을 몰아넣고 일부만 변하는것을 별도로 호출하여 해결하는 방식이다.

execute() : 변하지 않는 부분들(보조로직)
call() : 일부만 변하는 로직을 별도로 클래스를 선언하여 호출(핵심로직)
💛 템플릿 메서드를 적용해보자
템플릿 메서드 패턴은 이름 그대로 템플릿을 사용하는 방식이다. 템플릿은 기준이 되는 거대한 틀이라고 생각하자.
템플이라는 틀에 변하지않는 부분을 넣어놓고, 일부 변하는 부분을 별도로 호출하여 해결하는 패턴이다.
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("OrderController.request()");
//로직 호출
T result =call();
trace.end(status);
return result;
} catch (Exception e) {
trace.exception(status, e);
throw e;
}
}
protected abstract T call();
}
💚.AbstractTemplate<T> 클래스
어떤 작업을 처리하는 일부분을 서브 클래스로 캡슐화 해야하고, 기본적으로 전체 일을 수행하는 구조는 바뀌지 않아야하므로 abstract class으로 선언하여 추상클래스로둬야한다.
부모 클래스(AbstractTemplate)는 변하지않는 템플릿 코드를 가지고있고, 변하는 부분은 자식 클래스(implements 하고있는 클래스)에 두고 상속과 오버라이딩을 사용해서 처리한다.
💚.T execute()
기본적으로 전체일을 수행하는 구조. 이는 기본적으로 바뀌지않으며 T result = call();에 의해 바뀐다.
💚.protected abstract<T> call()
특정단계에서 수행하는 내역, 즉 서브 클래스로 캡슐화가 되어있고, 부모가 자식 클래스를 가져온다고 생각해야한다.
💛 템플릿 메서드를 사용하는 방법 2가지
💚 1. 캡슐화 되어있는 개별적으로 서브 클래스를 생성한다.
- SubClassLogic1 클래스
@Slf4j
public class SubClassLogic1 extends AbstractTemplate{
@Override
protected void call() {
log.info("비지니스 로직1 실행");
}
}
- SubClassLogic2 클래스
@Slf4j
public class SubClassLogic2 extends AbstractTemplate{
@Override
protected void call() {
log.info("비지니스 로직2 실행");
}
}
- 템플릿 메서드 패턴 적용
@Test
void templateMethod1(){
AbstractTemplate template1 = new SubClassLogic1();
template1.execute();
AbstractTemplate template2 = new SubClassLogic2();
template2.execute();
}
[결과 도출]

템플릿 메서드 패턴을 적용함으로써 핵심 로직과 보조 로직의 모듈화가 되어있는것을 지켜볼수있다.
그러나 수많은 클래스를 만들어야한다는 부분이 생긴다.
💚 2. 익명 내부 클래스를 선언하여 템플릿 메서드를 사용한다.
- OrderController
public String request(String itemId) {
AbstractTemplate<String> template = new AbstractTemplate<>(trace) {
@Override
protected String call() {
orderServiceV4.orderItem(itemId);
return "ok";
}
};
return template.execute("OrderController.request()");
}
- OrderService
AbstractTemplate<Void> template = new AbstractTemplate<>(trace) {
@Override
protected Void call() {
orderRepositoryV0.save(itemId);
return null;
}
};
template.execute("OrderService.orderItem()");
- OrderRepository
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");
익명 내부 클래스를 사용하여 객체를 생성하면서 AbstractTemplate를 상속받은 자식 클래스를 정의하였다. 즉, 별도의 자식 클래스를 직접 만들지 않아도된다는 장점이 존재한다.
AbstractTemplate<T> 의 제네릭 타입을 선언하여 어떤 객체가 들어와도 핸들링할수있다는 장점이 존재한다.
🧡 좋은설계란 무엇일까?
템플릿 메서드 패턴을 적용하다가 갑자기 좋은설계에 관한 이야기를 하는게 이상하게 느낄수있다. 하지만 템플릿 메서드 패턴과 좋은설계는 간접적인 연관성이 존재한다. 좋은 설계는 대게 변경이 일어날때 자연스럽게 드러난다고 한다.
만일 로그 남기는 로직이 변경이된다면 템플릿 메서드 패턴을 적용한 코드라면 템플릿 메서드 패턴을 적용한 AbstractTemplate 코드만 바꿔주면된다. 하지만 AbstractTemplate를 적용하지 않는다라면 모든 클래스를 다 찾아서 고쳐야한다. 끔찍하지 않는가?
이번에 구현한 코드는 코드의 가독성, 번잡함 뿐만아니라 로그를 남기는 부분에있어서 단일책임원칙(SRP)을 지킨 것이다. 변경 지점을 하나로 모아서 쉽게 대처할수 있는 구조를 만든 것이다.
❤. 결론과 회고
🧡 템플리 메서드 패턴의 정의
GOF디자인 패턴에서는 템플릿 메서드 패턴을 이렇게 정의한다.
"작업에서 알고리즘의 골격을 정의하고 일부 단계를 하위 클래스로 연기한다. 템플릿 메서드를 사용하면 하위 클래스가 알고리즘의 구조를 변경하지 않고도 알고리즘의 특정 단계를 재정의할수있다"

결국, 부모 클래스(AbstractTemplate)에 알고리즘의 골격인 템플릿을 정의하고, 일부 변경되는 로직은 자식 클래스에 정의하는것이다. 이렇게 하면 자식 클래스가 알고리즘의 전체 구조를 변경하지 않고, 특정 부분에만 재정의할수있다.
결국 상속과 오버라이딩을 통한 다형성으로 문제를 해결하는것이다.
🧡 템플릿 메서드 패턴의 치명적 단점
결국 템플릿 메서드 패턴도 상속을 사용한다는것이다. 상속은 부모클래스에 의존한다는 뜻. 하지만 코드를 아무리 읽어봐도 자식클래스는 부모클래스에 받는것은 없다.
자식 입장에서는 부모클래스의 기능을 전혀 사용하지 않지만 부모 클래스를 알아야한다. 이것은 좋은설계는 아니라고 볼수있다. 이런 잘못된 의존관계에 있어서 부모가 바뀌면 자식 클래스에도 영향을 미칠수있다는 점도 존재한다.
추가로 템플리 메서드 패턴은 별도의 클래스나 익명 내부 클래스를 만들어야하는 부분도 개선은되었지만 여전히 번잡하고 복잡하다.
템플리 메서드 패턴의 단점또한 존재한다는것이다. 다음번에는 비슷한 역할을 하지만 상속의 단점을 제거할수있는 디자인 패턴인 전략 패턴을 한번 공부해봐야겠다...
'🧶Spring > Tech-Stack' 카테고리의 다른 글
| [Tech-Stack] 전략 패턴(Strategy Pattern)이란 ? (2) | 2023.11.23 |
|---|---|
| [Tech Stack] 로그 추적기란? - 3(완료) (1) | 2023.11.22 |
| [Tech Stack] 로그 추적기란? - 2 (1) | 2023.11.21 |
| [Tech Stack] 로그 추적기란? - 1 (0) | 2023.11.20 |