팩토리 메서드 패턴
공통 작업 흐름은 유지하고, 그 안에서 사용할 객체의 생성만 하위 클래스에 맡기는 생성 패턴.
· 마지막 업데이트: 2026-10-25 · 5 min read
개요
같은 데이터를 CSV나 JSON 문자열로 바꾸는 내보내기 작업을 예로 들자. 이 변환을 맡는 객체를 기록기라고 하자. 작업 흐름은 같고 기록기만 달라진다면 흐름을 두 번 작성할 필요가 없다.
팩토리 메서드(Factory Method)는 사용할 객체를 만드는 메서드를 하위 클래스가 바꾸게 한다. 상위 클래스는 그 메서드가 돌려준 제품의 공통 약속만 사용한다.
UML
ExportJob.Run()은 Create()로 IWriter를 얻는다. CsvJob과 JsonJob이 Create()를 각각 구현하고, 상위 클래스의 Run()은 공유한다.
역사
GoF는 1994년 Design Patterns에서 Factory Method를 생성 패턴으로 정리했다. 객체를 만드는 선택을 하위 클래스로 미루는 구조다.1
왜 사용하는가?
새 기록기를 추가할 때 공통 작업 순서를 복사하지 않아도 된다. 생성 지점이 분리되어 실제 객체를 고르는 부분이 드러난다.
단순히 객체를 반환하는 정적 함수는 흔히 팩토리 함수라고 부르지만, 이 예제의 핵심은 상속을 통한 생성 메서드 재정의다. 이미 객체를 외부에서 주입받으면 상속 계층을 추가할 필요가 없을 수 있다.
예제
CsvJob이 CSV 기록기를 만들고, ExportJob의 공통 Run()이 이를 사용한다.
using System;
using System.Collections.Generic;
using System.Linq;
Console.WriteLine(new CsvJob().Run());
interface IWriter { string Write(string value); }
sealed class CsvWriter : IWriter
{
public string Write(string value) => "\"" + value.Replace("\"", "\"\"") + "\"";
}
sealed class JsonWriter : IWriter
{
public string Write(string value) => System.Text.Json.JsonSerializer.Serialize(new { value });
}
// 공통 실행 흐름은 생성 메서드가 반환한 약속만 사용한다.
abstract class ExportJob
{
protected abstract IWriter Create();
public string Run() => Create().Write("hello");
}
// 이 하위 클래스는 제품 선택만 바꾸며 실행 순서는 물려받는다.
sealed class CsvJob : ExportJob
{
protected override IWriter Create() => new CsvWriter();
}
sealed class JsonJob : ExportJob
{
protected override IWriter Create() => new JsonWriter();
}
"hello"Run()에는 CsvWriter를 생성하는 코드가 없다. 실행할 작업 종류를 정하는 곳과 작업 흐름이 분리된다.
범위 넓힌 예제
JSON 작업을 추가해 두 작업을 같은 ExportJob 타입으로 실행한다. 문자열의 따옴표 처리는 직접 조립하지 않고 JSON 직렬화기에 맡긴다.
using System;
using System.Collections.Generic;
using System.Linq;
foreach (ExportJob job in new ExportJob[] { new CsvJob(), new JsonJob() })
Console.WriteLine(job.Run());
interface IWriter { string Write(string value); }
sealed class CsvWriter : IWriter
{
public string Write(string value) => "\"" + value.Replace("\"", "\"\"") + "\"";
}
sealed class JsonWriter : IWriter
{
public string Write(string value) => System.Text.Json.JsonSerializer.Serialize(new { value });
}
// 공통 실행 흐름은 생성 메서드가 반환한 약속만 사용한다.
abstract class ExportJob
{
protected abstract IWriter Create();
public string Run() => Create().Write("hello");
}
// 이 하위 클래스는 제품 선택만 바꾸며 실행 순서는 물려받는다.
sealed class CsvJob : ExportJob
{
protected override IWriter Create() => new CsvWriter();
}
sealed class JsonJob : ExportJob
{
protected override IWriter Create() => new JsonWriter();
}
"hello"
{"value":"hello"}Run()은 그대로인데 Create()의 결과가 달라진다. 공통 알고리즘의 일부 단계를 바꾸는 Template Method 안에 Factory Method가 들어갈 수도 있다.
TypeScript 예제
ExportJob은 기록기를 만드는 시점과 사용하는 순서를 맡고, CsvJob·JsonJob은 어떤 기록기를 만들지 정한다. 변환 규칙을 추가하려고 공통 run()까지 복사할 필요가 없다.
interface IWriter { write(value: string): string; }
class CsvWriter implements IWriter {
write(value: string): string { return '"' + value.replaceAll('"', '""') + '"'; }
}
class JsonWriter implements IWriter {
write(value: string): string { return JSON.stringify({ value }); }
}
// 공통 실행 흐름은 생성 메서드가 반환한 약속만 사용한다.
abstract class ExportJob {
protected abstract create(): IWriter;
run(): string { return this.create().write("hello"); }
}
// 이 하위 클래스는 제품 선택만 바꾸며 실행 순서는 물려받는다.
class CsvJob extends ExportJob {
protected override create(): IWriter { return new CsvWriter(); }
}
class JsonJob extends ExportJob {
protected override create(): IWriter { return new JsonWriter(); }
}
for (const job of [new CsvJob(), new JsonJob()]) console.log(job.run());"hello"
{"value":"hello"}C#의 Create·Write는 create·write로 표기했다. 이 예제의 기록기는 파일에 쓰지 않고 CSV·JSON 문자열을 반환한다.
같이 보기
C# 예제는 각각 독립된 .NET 10 콘솔 프로그램이다.
TypeScript 예제는 ES2022 이상으로 컴파일한 JavaScript를 Node.js에서 실행한다.