1. 의도
템플릿 메서드는 부모 클래스에서 알고리즘의 골격을 정의하지만, 해당 알고리즘의 구조를 변경하지 않고 자식 클래스들이 알고리즘의 특정 단계들을 오버라이드(재정의)할 수 있도록 하는 행동 디자인 패턴입니다.
“큰 틀은 부모 클래스가 정하고, 세부사항은 자식 클래스가 채운다.”
어떤 일을 할 때 전체 흐름(순서)은 항상 같지만,
그 안에 들어가는 일부 과정은 상황마다 달라야 할 때가 있어요.
이럴 때 사용하는 게 템플릿 메서드입니다.
예시: 라면 끓이기🍜
우리가 라면을 끓이는 "알고리즘"은 보통 이렇죠:
1. 물 끓이기
2. 면 넣기
3. 스프 넣기 (종류에 따라 다름)
4. 계란, 치즈 등 넣기 (선택)
5. 불 끄기
이 전체 순서는 항상 동일하지만,
스프 종류나 토핑은 사람마다 다르겠죠?
이럴 때, 공통된 순서(= 알고리즘의 틀)는 부모 클래스에 두고,
스프나 토핑 같은 세부사항은 자식 클래스에서 정하도록 만드는 것,
이게 바로 템플릿 메서드 패턴입니다!
2. 문제
회사 문서들을 분석하는 데이터 마이닝 앱을 만들고 있다고 가정해 봅시다. 사용자들은 앱에 다양한 형식(PDF, DOC, CSV)의 문서들을 제공하고 앱은 이러한 문서들에서 일관된 형식으로 의미 있는 데이터를 추출하려고 시도합니다.
앱의 첫 번째 버전은 DOC 파일과만 작동할 수 있었고, 다음 버전에서는 CSV 파일을 지원할 수 있었습니다. 한 달 후, 당신은 앱이 PDF 파일에서 데이터를 추출하도록 '가르쳤습니다'.

어느 날 당신은 세 클래스 모두에 유사한 코드가 많다는 것을 알게 되었습니다. 다양한 데이터 형식들을 처리하는 코드는 클래스마다 완전히 다르지만 데이터 처리 및 분석을 위한 코드는 거의 같습니다. 알고리즘 구조는 그대로 두되, 코드 중복은 제거하는 게 좋지 않을까요?
PDF, DOC, CSV 같은 문서 형식이 다 다르지만, 처리 순서는 같아요:
- 파일 열기
- 데이터 추출하기
- 분석하기
- 보고서 만들기
- 파일 닫기
여기서 ‘파일 열기/닫기’와 ‘데이터 추출’은 형식마다 달라서 각자 구현해야 하고,
‘분석’과 ‘보고서 작성’은 거의 같아요.
이럴 땐 템플릿 메서드 패턴을 써서 공통 구조는 부모 클래스에,
문서 형식에 따라 다른 부분만 자식 클래스에 맡기는 거예요.
3. 해결책
템플릿 메서드 패턴은 알고리즘을 일련의 단계로 분해하고,
이 단계들을 각각 메서드로 정의한 다음,
이 메서드들을 하나의 “템플릿 메서드” 안에서 순서대로 호출하게 만듭니다.
각 단계는 크게 세 가지 유형으로 나눌 수 있습니다:
- 추상 메서드 (`abstract`): 자식 클래스가 반드시 구현해야 함
- 기본 구현 메서드 (`default`): 부모 클래스에서 제공하며, 자식이 필요 시 오버라이드
- 훅(`Hook`): 아무 동작도 하지 않는 “빈 메서드”로, 자식이 원하면 오버라이드
*훅(Hook) 메서드
- 훅은 기본적으로 “아무 일도 안 하는 메서드”예요.
- 부모 클래스에 있어도 자식 클래스가 필요할 때만 오버라이드해서 사용합니다.
- 보통 알고리즘의 전후 단계에서 확장 포인트로 쓰입니다.
protected void beforeReport() {} // 훅
자식 클래스에서:
@Override
protected void beforeReport() {
System.out.println("보고서 전처리 중...");
}
해결법
- 공통 알고리즘의 재사용: 템플릿 메서드 안의 고정된 흐름은 부모 클래스에서 한 번만 정의하면 됨
- 중복 제거: 여러 자식 클래스가 동일한 알고리즘 구조를 공유하면서, 반복되는 코드 줄일 수 있음
- 변화의 유연성 확보: 자식 클래스는 자신에게 필요한 부분만 구현/재정의하면 됨
- 파일 열기
- 데이터 추출
- 분석하기
- 보고서 작성
- 파일 닫기
여기서 파일 열기/닫기, 데이터 추출은 형식마다 다르기 때문에 → `abstract` 메서드로 둡니다.
하지만 “분석”과 “보고서 생성”은 대부분 같기 때문에 → 부모 클래스에서 디폴트 구현할 수 있습니다.

// 부모 클래스
abstract class DocumentParser {
// 템플릿 메서드: 순서를 정의한 메서드
public final void parse() {
openFile(); // 형식마다 다름 → 추상
extractData(); // 형식마다 다름 → 추상
analyzeData(); // 공통이니까 디폴트 구현
generateReport(); // 이것도 기본 제공
closeFile(); // 파일 닫기 - 형식마다 다름
}
protected abstract void openFile();
protected abstract void extractData();
*protected void analyzeData() {
System.out.println("데이터 분석 중...");
}
protected void generateReport() {
System.out.println("보고서 생성 중...");
}*
protected void closeFile() {
System.out.println("파일 닫기 완료");
}
}
// 자식 클래스: CSV
class CSVParser extends DocumentParser {
protected void openFile() {
System.out.println("CSV 파일 열기");
}
protected void extractData() {
System.out.println("CSV 데이터 추출");
}
}
// 자식 클래스: PDF
class PDFParser extends DocumentParser {
protected void openFile() {
System.out.println("PDF 파일 열기");
}
protected void extractData() {
System.out.println("PDF 데이터 추출");
}
// 분석 방식 다르게 하고 싶다면 오버라이드 가능
protected void analyzeData() {
System.out.println("PDF 데이터 특별 분석");
}
}
4.구조

1. 추상 클래스 (부모 클래스)
- 템플릿 메서드의 전체 흐름(알고리즘의 틀)을 정의합니다.
- 그리고 그 안에서 호출되는 단계들을 메서드로 분리해 둡니다.
- 이 단계들은 abstract(추상 메서드)로 선언되거나, 기본 동작이 있는 메서드일 수도 있어요.
abstract class HouseBuilder {
public final void buildHouse() { // 템플릿 메서드
layFoundation();
buildWalls();
installRoof();
paint();
}
abstract void layFoundation(); // 추상 단계
abstract void buildWalls(); // 추상 단계
void installRoof() { // 기본 구현
System.out.println("기본 지붕 설치");
}
void paint() {
// 선택 사항, 필요하면 오버라이드 가능
}
}
2. 구상 클래스들 (자식 클래스들)
- 부모 클래스에서 정의한 추상 단계들을 자신만의 방식으로 구현합니다.
- 템플릿 메서드 자체는 건드리지 않습니다. (순서나 전체 구조는 그대로 유지)
class WoodenHouseBuilder extends HouseBuilder {
void layFoundation() {
System.out.println("나무 기초 쌓기");
}
void buildWalls() {
System.out.println("나무 벽 세우기");
}
void paint() {
System.out.println("목재 전용 도료로 페인트");
}
}
5. 템플릿 메서드 패턴은 언제 쓰면 좋을까?
- 알고리즘의 전체 흐름은 고정하고,
일부 단계만 바꿀 수 있게 만들고 싶을 때
→ 예: 파일 처리 순서는 같지만, PDF/DOC/CSV마다 파싱 방식이 다를 때 - 하나의 덩어리(모놀리식) 알고리즘을 단계별로 나누고 싶을 때
→ 공통된 순서는 부모 클래스에, 세부 동작은 자식 클래스에서 구현 - 비슷한 알고리즘을 가진 클래스가 여러 개 있을 때
→ 공통 구조는 부모에 모으고, 차이점만 각 자식에 맡기면 유지보수가 쉬워져요. - 중복 코드를 줄이고, 코드 구조를 깔끔하게 재사용하고 싶을 때
→ 자주 겹치는 단계들은 부모 클래스에 올려서 코드 재사용성 향상!
6.구현 방법
1단계. 알고리즘 흐름을 분석하자
- 전체 알고리즘을 여러 단계(step)로 나눌 수 있는지 확인해보세요.
- 각 단계 중 어떤 것은 항상 공통되고, 어떤 것은 상황에 따라 달라지는지 구분합니다.
예: "파일 열기 → 데이터 추출 → 분석 → 보고서 작성 → 파일 닫기"
2단계. 추상 클래스(부모 클래스)를 만든다
- 전체 흐름을 정의하는 템플릿 메서드를 작성하세요.
- 이 메서드는 각 단계를 순서대로 호출합니다.
- 바뀌는 단계는 abstract 메서드로 선언하고,
- 고정되거나 기본 동작이 가능한 단계는 일반 메서드로 정의하세요.
- 이 템플릿 메서드는 보통 `final`로 만들어 자식이 못 바꾸게 막습니다.
📌 목적: 순서를 통제하고, 내용은 자식에게 위임하기
3단계. 필요에 따라 디폴트 구현을 추가한다
- 모든 단계가 추상이어도 되지만,
- 자주 겹치는 로직이 있다면 부모 클래스에 기본 구현을 넣는 것이 효율적입니다.
- 자식 클래스가 꼭 오버라이드하지 않아도 되도록 유연성을 주는 거죠.
4단계. “훅(hook)”을 고려해보자
- 훅은 빈 메서드(선택적 단계)입니다.
- 자식 클래스가 필요할 때만 오버라이드하면 됩니다.
- 보통 알고리즘의 시작 전/후나 중간에 확장 포인트로 삽입합니다.
예: beforeSave(), afterParse() 같은 메서드들
5단계. 자식 클래스들을 만들고 필요한 단계만 구현한다
- 각 자식 클래스는 모든 추상 메서드를 반드시 구현해야 합니다.
- 디폴트 구현이 있는 메서드나 훅은 원하면 오버라이드, 아니면 그대로 사용합니다.
7. 장단점
장점
- 부분만 바꿔 쓸 수 있어요
- 알고리즘 전체를 건드리지 않고, 필요한 단계만 오버라이드하면 됩니다. 그래서 유지보수가 쉬워요.
- 중복 코드 제거
- 공통되는 로직은 부모 클래스에 한 번만 작성하면 돼요. 자식 클래스들은 필요한 부분만 구현하면 되니 코드가 깔끔해져요.
- 구조가 명확해짐
- 전체 흐름은 하나의 템플릿 메서드에 있고, 각 단계가 잘게 나뉘어 있어서 가독성과 이해도가 높아요.
단점
- 유연성이 부족
- 전체 알고리즘 흐름은 부모 클래스에서 고정되기 때문에, 흐름 자체를 바꾸고 싶은 경우엔 어렵습니다.
- 리스코프 치환 원칙 위반 가능
- 자식 클래스가 부모 클래스의 동작을 따르지 않고, 의도치 않게 깨뜨릴 수도 있어요. (예: 훅 메서드 무시하거나 흐름을 바꾸는 식으로)
- 단계가 많아지면 복잡해짐
- 알고리즘을 너무 세세하게 나누면, 오히려 코드가 복잡하고 관리하기 어려워질 수 있어요.
8. 다른 패턴과의 관계
팩토리 메서드 패턴과의 관계
- 팩토리 메서드는 템플릿 메서드의 한 부분(단계)처럼 쓰일 수 있어요.
- 즉, 템플릿 메서드 안의 어떤 “객체 생성” 단계만 따로 떼어내면
→ 팩토리 메서드로 표현 가능!
참고