+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|"hello"
EMPTY_INPUT
TypeScript에는 C# 비가상 메서드와 같은 final 제한이 없으므로 여기서는 run()을 재정의하지 않고 format()만 구현한다. 빈 입력은 변환기를 호출하기 전에 실패 결과를 반환한다. 비교에 쓴 입력은 ASCII이며 Unicode 공백·대소문자 규칙은 두 런타임에서 다를 수 있다.
상속 대신 함수와 LINQ로 조합하기
달라지는 단계가 문자열 변환 하나라면 함수를 인자로 받는 편이 간단하다. Run은 입력 정리와 빈 값 검사를 계속 맡고, 호출부가 변환 함수만 전달한다. LINQ의 Select는 각 입력에 같은 흐름을 적용한다.
C#
using System;
using System.Linq;
using System.Text.Json;
ExportResult Run(string input, Func<string, string> format)
{
string value = input.Trim();
if (value.Length == 0) return new ExportResult(false, "EMPTY_INPUT");
return new ExportResult(true, "REPORT|" + format(value));
}
var inputs = new[] { " hello ", " " };
// 형식만 전달하며, 빈 입력은 변환 함수를 호출하기 전에 실패한다.
var results = inputs.Select(input => Run(input, value => JsonSerializer.Serialize(value)))
.ToArray();
foreach (ExportResult result in results) Console.WriteLine(result.Text);
sealed record ExportResult(bool Success, string Text);
TypeScript
type ExportResult = { success: true; text: string } | { success: false; code: "EMPTY_INPUT" };
function run(input: string, format: (value: string) => string): ExportResult {
const value = input.trim();
if (value.length === 0) return { success: false, code: "EMPTY_INPUT" };
return { success: true, text: "REPORT|" + format(value) };
}
const inputs = [" hello ", " "];
// 공통 흐름은 한 함수에 두고 각 입력에 적용한다.
const results = inputs.map(input => run(input, value => JSON.stringify(value)));
for (const result of results) console.log(result.success ? result.text : result.code);
REPORT|"hello"
EMPTY_INPUT
상속형 Template Method는 하위 클래스가 부모의 흐름과 보호 멤버에 묶인다. 훅과 하위 클래스를 계속 늘려야 한다면 단순한 변환도 부모 변경의 영향을 받게 되므로 함수 전달이나 Strategy로 나누는 편이 낫다. 이 대안에서도 정리→검사→변환 순서와 실패 결과는 유지한다.
Microsoft: Introduction to LINQ queries