여느 때와 같이 운영업무를 진행하고 있었는데, 특정 필드의 수정이력을 쌓아달라는 요청이 왔다.
그래서 우리 프로젝트의 History를 쌓는 로직을 살펴보고 있었는데, 도서에서만 접한 Template Method Pattern이 적용되어 있었다.
그래서 이번 주제는 Template Method Pattern에 대해 간단하게 설명하고 실무에 적용된 Template Method Pattern 예제를 통해 도입을 고민하는 분들에게 조금이나마 도움을 주고싶다.
Template Method Pattern 설명
GoF의 디자인 패턴 도서에서는 템플릿 메서드를 아래와 같이 정의하고 있다.
"템플릿 메서드 패턴은 알고리즘의 골격을 정의하고, 일부 단계를 서브클래스에서 구현하도록 한다. 즉, 알고리즘의 구조는 변경하지 않으면서, 특정 단계의 구현을 하위 클래스에 위임할 수 있다."
패턴을 알고 있을 때는 이 정의가 정말 와닿지만, 사실 처음에 봤을 때는 그리 와닿지 않는다. 그래서 일상, 햄버거 예제를 통해 패턴의 이해를 쉽게 도와주겠다!
예제 1) 일상 속 템플릿 메서드 패턴
우리는 사실 일상 속에서 빈번하게 템플릿 메서드 패턴을 살펴볼 수 있다. 특히, 취업을 준비하면서 가장 고통스러운 게 무엇인가를 생각해 보면 바로 자기소개서를 작성하는 것이다. 자기소개서는 보통 이런 식의 문항을 지정해서 제공한다.
1. OO기업에 지원하신 동기는 무엇인가요? (1000자 이내)
2. 성격의 장단점이 무엇인가요? (2000자 이내)
기업에서 이와 같은 자기소개서 문항을 지정해 두면 지원자 개성에 따라 다양한 답변을 작성하게 된다.

이처럼 회사가 정의한 자기소개서 문항(템플릿)에서 지원자들이 각각 다른 답변을 도출하는 것이 일상 속 템플릿 메서드 패턴의 형태라고 생각한다.
예제 2) 롯*리아 예제코드로 알아보는 템플릿 메서드 패턴
롯*리아에서 버거를 만들 때, 빵 굽기 > 주재료 넣기 > 토핑 추가하기 > 햄버거 완성 프로세스가 있다고 생각해 보자.
그렇다면 불고기버거, 새우버거, 치킨버거는 패티만 다르기 때문에 주재료 넣기 과정을 제외하고 공통작업을 진행한다.
주재료 넣기를 제외한 공통작업을 상위클래스에 구현하고 주재료 넣기라는 추상 메서드를 하위클래스에서 구현해 보겠다.
abstract class LotteriaBurger {
public final void makeBurger() {
prepareBun();
addMainIngredient();
addToppings();
System.out.println("🍔 완성!\n");
}
private void prepareBun() {
System.out.println("빵 노릇노릇 굽기");
}
private void addToppings(){
System.out.println("야채, 소스 토핑 추가하기");
}
// 주재료 추가하는 method
protected abstract void addMainIngredient();
}
class BulgogiBurger extends LotteriaBurger {
@Override
protected void addMainIngredient() {
System.out.println("[불고기] 패티 추가하기");
}
}
class ShrimpBurger extends LotteriaBurger {
@Override
protected void addMainIngredient() {
System.out.println("[새우] 패티 추가하기");
}
}
class ChickenBurger extends LotteriaBurger {
@Override
protected void addMainIngredient() {
System.out.println("[치킨] 패티 추가하기");
}
}
이런 식으로 구현할 수 있다. 주재료 method만 상속받아 각각 class에 맞는 주재료를 넣어주고 있다.
=> bulgogiBurger.makeBurger();
=> 결과
빵 노릇노릇 굽기
[불고기] 패티 추가하기
야채, 소스 토핑 추가하기
🍔 완성!
=> shrimpBurger.makeBurger();
=> 결과
빵 노릇노릇 굽기
[새우] 패티 추가하기
야채, 소스 토핑 추가하기
🍔 완성!
=> chickenBurger.makeBurger();
=> 결과
빵 노릇노릇 굽기
[치킨] 패티 추가하기
야채, 소스 토핑 추가하기
🍔 완성!
햄버거 예제를 클래스다이어그램으로 그려본다면 아래와 같은 구조가 나타날 것이다.

두 가지 예제를 통해 Template Method Pattern에 대해 알아보았다. 이제 다시 정의를 살펴보면
"템플릿 메서드 패턴은 알고리즘의 골격을 정의하고, 일부 단계를 서브클래스에서 구현하도록 한다. 즉, 알고리즘의 구조는 변경하지 않으면서, 특정 단계의 구현을 하위 클래스에 위임할 수 있다."
이전과는 다르게 개념에 대해 확실하게 이해할 수 있을 것이다.
실무에서 사용된 Template Method Pattern
이제 템플릿 메서드 패턴에 대해 이해가 되었을 것이라고 생각한다. 사실 이 글을 읽고 있는 독자 대부분이 해당 패턴을 공부하고 도입해 보기 위함이라고 생각한다. 그렇기에 우리 프로젝트에서 어떻게 적용했는지 소개하고 분석하는 시간을 가져보려고 한다.
1) 알림에 적용된 템플릿 메서드 패턴

우리 팀에는 여러 가지 알림 서비스를 제공하고 있다.
예를 들어 결제나 가입을 하고 나면 SMS, 카카오톡, 이메일을 통해 알림을 보내주고 있다. 우리 팀의 알림 순서는 아래와 같다.
🔔 알림발송 순서
Step1. 알림을 정상적으로 보낼 수 있는지 요청 값을 검증한다.
Step2. 알림을 보내기 전, 로그를 적재한다.
Step3. 고정메시지를 추가하여 Message를 가공한다.
Step4. 알림을 발송한다.
Step5. 정상적으로 수행되었는지? 오류가 발생했는지? 정보를 테이블에 저장한다.
사실 생각해 보면, 알림을 보내는 방법(KaKaoTalk, SMS)과 검증(유효이메일, 유효전화번호)만 다르지 2, 3, 5번 과정은 동일하다. 그렇기 때문에 알림을 보내기 전, 보낼 수 있는지 체크하는 canSend함수와 알림을 보내는 send함수만 추상메서드로 구현했다.
* 참고) 추상 클래스를 사용하는 이유
추상 클래스를 사용하는 이유는 공통적인 설계를 제공하고, 특정 메서드의 구현을 자식 클래스에서 강제하기 위해서이다. 따라서, 추상 클래스는 직접적으로 인스턴스를 생성할 수 없으며, 반드시 구현해야 하는 추상 메서드(abstract method)를 포함할 수 있다. 이를 위해
abstract 키워드를 사용하여 클래스를 정의한다.
2) 이력적재에 적용된 템플릿 메서드 패턴

특정 테이블에 수정요청이 들어오면 History 테이블에 이력을 적재하고 있다. 만약, 상품 정보가 변경되었다면 ProductHistory 테이블에 변경사항을 저장하고 가게 정보가 변경되었다면 StoreHistory 테이블에 저장하고 있다. 변경이력을 적재하는 순서는 아래와 같다.
📜 변경이력적재 순서
Step1. 변경 전, 데이터를 조회한다.
Step2. 변경 전, 데이터와 변경할 데이터를 비교한다.
Step3. 변경사항이 있는 데이터 정보를 테이블에 저장한다.
실제 구현된 내용과는 차이가 있지만, write 함수를 구현한다면
abstract class AbstractHistoryWriter {
public final void write(long id, Object newValue) {
// 1. 변경 전, 데이터 조회
Object original = getOriginalValue(id);
// 2. 변경 전, 데이터와 변경할 데이터를 비교 후, List형태로 반환
List<History> historyList = compareValues(original, newValue);
// 3. 변경사항이 있는 데이터 정보를 테이블에 저장한다.
if (!historyList.isEmpty()) {
insertHistory(historyList);
}
}
// 생략 ...
}
이렇게 개발할 수 있을 것이다.
오늘의 마무리
팀에 오자마자 관심있던 백엔드 아키텍쳐를 분석했다. 우리팀 서비스의 아키텍쳐는 추상화가 많이 되어있고 다양한 디자인 패턴이 적용되어 있다보니 처음 분석할 때 이해하기가 매우 어려운편이였다. 그래서 디자인 패턴을 사용하는 것이 정말 클린 코드와 클린 아키텍처를 지향한다고 할 수 있을까? 라는 의문이 들었다. 왜냐면 패턴을 이해하지못하면 전체적인 구조를 파악하기 어렵다. 그럼에도 위 2가지의 예제처럼 적재적소에 사용된다면 나쁠건 없다고 생각한다.
아.. 놀라운 사실이 있다. 백엔드 아키텍쳐를 설계하신분의 커밋을 추적하니 무려 5년전에 설계하고 개발되었다. 몇몇은 안티패턴이라고 할지라도 이정도로 고민하고 디자인 패턴을 도입 및 설계했다는 사실에 놀랐다. 이를 계기로 이제서야 공부하는 내자신을 돌이켜볼 수 있었다.
아키텍트를 설계한분은 지금 뭘하고있는진 모르겠지만, 이분처럼 전반적인 아키텍쳐를 설계해보고 싶은 목표가 생겼다!!
'SPRING' 카테고리의 다른 글
MyBatis와 Entity 생성자에 대한 고찰 (2) | 2025.03.08 |
---|---|
LIKE Wildcard 검색을 막아보자(MySQL, MyBatis) (2) | 2024.12.25 |
Spring AI를 사용해보자. (3) | 2024.11.17 |
[MyBatis] Loop vs Subquery vs Inner Join (3) | 2024.07.20 |
MyBatis에서 Helper 클래스 적용하기 (StringUtils, CollectionUtils...) (2) | 2024.06.23 |