템플릿 메서드 패턴
작업 순서는 기반 클래스에 고정하고 일부 단계를 하위 클래스가 구현하는 행위 패턴.
· 마지막 업데이트: 2026-10-25 · 6 min read
개요
보고서를 만들 때 입력 정리, 빈 값 검사, 형식 변환의 순서는 늘 같다고 하자. 형식마다 이 순서를 복사하면 한 구현에서 검사를 빠뜨리거나 순서를 바꾸기 쉽다.
템플릿 메서드(Template Method)는 알고리즘의 뼈대를 메서드 하나에 두고 바뀌는 단계만 하위 클래스에 맡긴다. 여기서 템플릿은 문서 파일이 아니라 실행 순서의 틀이라는 뜻이다.
UML
ReportTemplate.Run()이 입력 정리와 검사를 수행한 뒤 Format()을 부른다. UpperReport와 JsonReport는 Format()만 구현하며 Run()은 재정의하지 않는다.
역사
GoF는 1994년 Design Patterns에서 Template Method를 행위 패턴으로 정리했다. 알고리즘의 구조를 유지한 채 일부 단계를 하위 클래스가 바꾸게 하는 방식이다.1
왜 사용하는가?
반드시 수행해야 할 순서를 한곳에서 읽을 수 있고, 각 구현은 표현 단계에 집중한다. Run()은 virtual이 아니므로 이 호출 경로의 단계 순서를 하위 클래스가 재정의하지 않는다.
상속으로 단계를 바꾸므로 실행 중에 같은 객체의 처리 방식을 바꿔 끼우기에는 제약이 있다. 처리 규칙을 객체나 함수로 전달하는 전략 패턴과 선택 기준이 다르다.
예제
양쪽 공백을 제거한 뒤 대문자 형식으로 보고서를 만든다.
using System;
using System.Collections.Generic;
using System.Linq;
ExportResult result = new UpperReport().Run(" hello ");
Console.WriteLine(result.Success + ": " + result.Text);
sealed record ExportResult(bool Success, string Text);
// 반복되는 순서와 검사 규칙은 기반 클래스 한곳에서 관리한다.
abstract class ReportTemplate
{
public ExportResult Run(string input)
{
string value = input.Trim();
if (value.Length == 0) return new ExportResult(false, "EMPTY_INPUT");
return new ExportResult(true, "REPORT|" + Format(value));
}
protected abstract string Format(string value);
}
// 하위 클래스는 공통 흐름 전체가 아니라 이 변환 단계만 바꾼다.
sealed class UpperReport : ReportTemplate
{
protected override string Format(string value) => value.ToUpperInvariant();
}
sealed class JsonReport : ReportTemplate
{
protected override string Format(string value) => System.Text.Json.JsonSerializer.Serialize(value);
}
True: REPORT|HELLORun()은 정리·검사·변환 순서를 소유하고, UpperReport에는 대문자로 바꾸는 단계만 있다.
범위 넓힌 예제
JSON 형식 구현을 사용하고 빈 입력도 전달한다. 빈 값은 Format()을 부르기 전에 실패 결과로 반환한다.
using System;
using System.Collections.Generic;
using System.Linq;
ReportTemplate report = new JsonReport();
foreach (string input in new[] { " hello ", " " })
{
ExportResult result = report.Run(input);
Console.WriteLine(result.Success + ": " + result.Text);
}
sealed record ExportResult(bool Success, string Text);
// 반복되는 순서와 검사 규칙은 기반 클래스 한곳에서 관리한다.
abstract class ReportTemplate
{
public ExportResult Run(string input)
{
string value = input.Trim();
if (value.Length == 0) return new ExportResult(false, "EMPTY_INPUT");
return new ExportResult(true, "REPORT|" + Format(value));
}
protected abstract string Format(string value);
}
// 하위 클래스는 공통 흐름 전체가 아니라 이 변환 단계만 바꾼다.
sealed class UpperReport : ReportTemplate
{
protected override string Format(string value) => value.ToUpperInvariant();
}
sealed class JsonReport : ReportTemplate
{
protected override string Format(string value) => System.Text.Json.JsonSerializer.Serialize(value);
}
True: REPORT|"hello"
False: EMPTY_INPUT성공 여부와 결과 문자열을 나누어 잘못된 입력을 정상 보고서로 취급하지 않는다. 공통 흐름이 없는데 단순한 함수 하나를 위해 상속 계층을 만드는 것은 피할 수 있다.
TypeScript 예제
입력 정리·빈 값 검사·접두사 추가는 기반 클래스에서 한 번 정하고, 형식마다 달라지는 변환만 format()으로 나눈다. 각 형식이 검사를 복사하다 빠뜨리지 않으면서 변환 규칙만 바꾸도록 한 구조다.
type ExportResult = { success: true; text: string } | { success: false; code: "EMPTY_INPUT" };
// 반복되는 순서와 검사 규칙은 기반 클래스 한곳에서 관리한다.
abstract class ReportTemplate {
run(value: string): ExportResult {
const clean = value.trim();
if (clean.length === 0) return { success: false, code: "EMPTY_INPUT" };
return { success: true, text: "REPORT|" + this.format(clean) };
}
protected abstract format(value: string): string;
}
// 하위 클래스는 공통 흐름 전체가 아니라 이 변환 단계만 바꾼다.
class UpperReport extends ReportTemplate {
protected override format(value: string): string { return value.toUpperCase(); }
}
class JsonReport extends ReportTemplate {
protected override format(value: string): string { return JSON.stringify({ value }); }
}
for (const report of [new UpperReport(), new JsonReport()]) {
const result = report.run(" hello ");
console.log(result.success ? result.text : result.code);
}
const empty = new UpperReport().run(" ");
console.log(empty.success ? empty.text : empty.code);REPORT|HELLO
REPORT|{"value":"hello"}
EMPTY_INPUTTypeScript에는 C# 비가상 메서드와 같은 final 제한이 없으므로 여기서는 run()을 재정의하지 않고 format()만 구현한다. 빈 입력은 변환기를 호출하기 전에 실패 결과를 반환한다.
같이 보기
C# 예제는 각각 독립된 .NET 10 콘솔 프로그램이다.
TypeScript 예제는 ES2022 이상으로 컴파일한 JavaScript를 Node.js에서 실행한다.