템플릿 메서드 패턴
개요
보고서를 만들 때 입력 정리, 빈 값 검사, 형식 변환의 순서는 늘 같다고 하자. 형식마다 이 순서를 복사하면 한 구현에서 검사를 빠뜨리거나 순서를 바꾸기 쉽다.
템플릿 메서드(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성공 여부와 결과 문자열을 나누어 잘못된 입력을 정상 보고서로 취급하지 않는다. 공통 흐름이 없는데 단순한 함수 하나를 위해 상속 계층을 만드는 것은 피할 수 있다.
같이 보기
각 예제는 독립된 .NET 10 콘솔 프로그램이다.