팩토리 메서드 패턴
개요
같은 데이터를 내보내더라도 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가 들어갈 수도 있다.
같이 보기
각 예제는 독립된 .NET 10 콘솔 프로그램이다.