추상 팩토리 패턴
서로 함께 쓸 제품들을 같은 계열로 생성해, 화면마다 구현 선택을 반복하지 않게 하는 생성 패턴.
· 마지막 업데이트: 2026-10-25 · 6 min read
개요
테마가 있는 화면을 예로 들자. 밝은 테마의 버튼을 골랐는데 체크박스만 어두운 테마라면 화면 구성이 어긋난다. 버튼 하나를 만드는 방법보다, 함께 쓰는 부품들을 같은 계열로 고르는 규칙이 필요하다.
추상 팩토리(Abstract Factory)는 관련 제품을 만드는 약속을 한곳에 모은다. 화면은 구체적인 버튼 클래스를 고르는 대신 팩토리에 필요한 부품을 요청한다.
UML
Dialog는 IWidgetFactory에서 버튼과 체크박스를 받는다. LightFactory가 같은 테마의 두 제품을 만들고, 화면은 각 제품의 Draw()만 사용한다.
역사
GoF는 1994년 Design Patterns에서 Abstract Factory를 생성 패턴으로 정리했다. 서로 다른 사용자 인터페이스 계열을 지원하는 도구가 대표적인 적용 상황이다.1
왜 사용하는가?
화면마다 테마를 검사하고 버튼과 체크박스를 따로 고르면 같은 선택 코드가 퍼진다. 팩토리를 처음 조립할 때 정해 두면, 화면은 사용할 부품만 요청하면 된다.
제품 종류를 새로 추가할 때는 팩토리 계약과 각 계열 구현을 함께 고쳐야 한다. 계열을 바꾸는 일이 잦고 그 안의 제품 종류가 비교적 안정적일 때 잘 맞는다.
예제
밝은 테마의 팩토리로 화면 하나를 만든다. 제품은 실제 GUI 대신 이름을 반환해 조합을 보여 준다.
using System;
using System.Collections.Generic;
using System.Linq;
Console.WriteLine(new Dialog(new LightFactory()).Render());
interface IButton { string Draw(); }
interface ICheckbox { string Draw(); }
// 테마 선택을 화면에서 떼어 내고 관련 제품의 생성 약속으로 모은다.
interface IWidgetFactory
{
IButton Button();
ICheckbox Checkbox();
}
sealed class LightButton : IButton { public string Draw() => "light button"; }
sealed class LightCheckbox : ICheckbox { public string Draw() => "light checkbox"; }
sealed class DarkButton : IButton { public string Draw() => "dark button"; }
sealed class DarkCheckbox : ICheckbox { public string Draw() => "dark checkbox"; }
sealed class LightFactory : IWidgetFactory
{
public IButton Button() => new LightButton();
public ICheckbox Checkbox() => new LightCheckbox();
}
sealed class DarkFactory : IWidgetFactory
{
public IButton Button() => new DarkButton();
public ICheckbox Checkbox() => new DarkCheckbox();
}
// 화면은 선택된 제품을 사용할 뿐 구체 테마 클래스를 만들지 않는다.
sealed class Dialog(IWidgetFactory factory)
{
public string Render() => factory.Button().Draw() + " + " + factory.Checkbox().Draw();
}
light button + light checkboxDialog에는 LightButton을 직접 만드는 코드가 없다. 버튼과 체크박스의 구체적인 선택은 팩토리가 맡는다.
범위 넓힌 예제
이번에는 같은 Dialog에 두 팩토리를 차례로 넘긴다. 화면 코드는 그대로고, 함께 생성되는 제품의 계열만 달라진다.
using System;
using System.Collections.Generic;
using System.Linq;
foreach (IWidgetFactory factory in new IWidgetFactory[] { new LightFactory(), new DarkFactory() })
Console.WriteLine(new Dialog(factory).Render());
interface IButton { string Draw(); }
interface ICheckbox { string Draw(); }
// 테마 선택을 화면에서 떼어 내고 관련 제품의 생성 약속으로 모은다.
interface IWidgetFactory
{
IButton Button();
ICheckbox Checkbox();
}
sealed class LightButton : IButton { public string Draw() => "light button"; }
sealed class LightCheckbox : ICheckbox { public string Draw() => "light checkbox"; }
sealed class DarkButton : IButton { public string Draw() => "dark button"; }
sealed class DarkCheckbox : ICheckbox { public string Draw() => "dark checkbox"; }
sealed class LightFactory : IWidgetFactory
{
public IButton Button() => new LightButton();
public ICheckbox Checkbox() => new LightCheckbox();
}
sealed class DarkFactory : IWidgetFactory
{
public IButton Button() => new DarkButton();
public ICheckbox Checkbox() => new DarkCheckbox();
}
// 화면은 선택된 제품을 사용할 뿐 구체 테마 클래스를 만들지 않는다.
sealed class Dialog(IWidgetFactory factory)
{
public string Render() => factory.Button().Draw() + " + " + factory.Checkbox().Draw();
}
light button + light checkbox
dark button + dark checkboxDarkFactory도 같은 생성 약속을 구현하므로 Dialog는 두 제품을 같은 방식으로 쓴다. 이 예제의 테마 일관성은 각 팩토리 구현이 지키며, 인터페이스 이름만으로 잘못 섞인 제품을 자동 검출하지는 않는다.
TypeScript 예제
Dialog가 테마 선택과 각 부품의 그리기까지 맡으면 새 테마를 넣을 때 화면 코드도 바뀐다. IWidgetFactory는 함께 쓸 부품을 고르고, Dialog는 그 부품을 사용하는 일만 맡는다.
interface IButton { draw(): string; }
interface ICheckbox { draw(): string; }
// 테마 선택을 화면에서 떼어 내고 관련 제품의 생성 약속으로 모은다.
interface IWidgetFactory {
button(): IButton;
checkbox(): ICheckbox;
}
class LightButton implements IButton { draw(): string { return "light button"; } }
class LightCheckbox implements ICheckbox { draw(): string { return "light checkbox"; } }
class DarkButton implements IButton { draw(): string { return "dark button"; } }
class DarkCheckbox implements ICheckbox { draw(): string { return "dark checkbox"; } }
class LightFactory implements IWidgetFactory {
button(): IButton { return new LightButton(); }
checkbox(): ICheckbox { return new LightCheckbox(); }
}
class DarkFactory implements IWidgetFactory {
button(): IButton { return new DarkButton(); }
checkbox(): ICheckbox { return new DarkCheckbox(); }
}
// 화면은 선택된 제품을 사용할 뿐 구체 테마 클래스를 만들지 않는다.
class Dialog {
constructor(private readonly factory: IWidgetFactory) {}
render(): string {
return this.factory.button().draw() + " + " + this.factory.checkbox().draw();
}
}
for (const factory of [new LightFactory(), new DarkFactory()]) {
console.log(new Dialog(factory).render());
}light button + light checkbox
dark button + dark checkbox같은 역할의 C# 인터페이스를 TypeScript 인터페이스로 옮겼다. 테마가 섞이지 않게 제품을 돌려주는 책임은 각 팩토리 구현에 있다.
같이 보기
C# 예제는 각각 독립된 .NET 10 콘솔 프로그램이다.
TypeScript 예제는 ES2022 이상으로 컴파일한 JavaScript를 Node.js에서 실행한다.