Abstract Factory
Abstract Factory는 구체적인 클래스를 지정하지 않고, 서로 관련 있는 객체들의 제품군(family)을 한꺼번에 생성할 수 있게 해주는 생성 패턴입니다. 클라이언트 코드는 팩토리 인터페이스에만 의존하므로, 어떤 제품군이 만들어지는지는 런타임에 결정됩니다.
▶아키텍처 다이어그램
🔍 구조 다이어그램점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다
크로스 플랫폼 UI를 만들기 시작하면 버튼, 체크박스, 텍스트 필드가 플랫폼마다 한 세트씩 필요해집니다. 그런데 생성 지점마다 if (os === 'windows') new WindowsButton() 같은 분기를 넣기 시작하면, 버튼을 만들 때도 분기하고 체크박스를 만들 때도 분기합니다. 여기에 Linux가 추가되는 순간 그 분기가 화면 곳곳으로 번집니다.
더 큰 문제는 조합이 어긋나는 순간입니다. Windows 버튼 옆에 macOS 체크박스가 섞이면 동작도 모양도 일관성을 잃습니다. 관련 객체가 항상 같은 변형(variant)에서 나와야 한다는 규칙이 코드 구조에 없기 때문입니다.
처음에는 버튼 하나, 다이얼로그 하나를 직접 new로 만들어도 큰 문제가 잘 드러나지 않습니다. 그런데 제품이 여러 플랫폼과 테마를 동시에 지원하기 시작하면 생성 로직이 화면 코드와 비즈니스 로직 곳곳으로 퍼집니다. 플랫폼 하나를 더 추가할 때마다 관련 컴포넌트를 만드는 모든 분기를 다시 건드려야 하고, 서로 같이 움직여야 할 객체들의 조합도 쉽게 어긋납니다.
Abstract Factory는 이런 압력에서 나온 패턴입니다. 객체 하나만 바꿔 끼우는 것으로는 부족하고, 서로 맞물려야 하는 제품군 전체를 한 번에 바꿔 끼워야 할 때 생성 책임을 한곳에 모으자는 접근입니다. Factory Method가 개별 생성 지점을 여는 패턴이라면, Abstract Factory는 제품군 단위로 일관성을 묶는 패턴이라고 읽으면 됩니다.
Abstract Factory의 핵심은 두 가지 축입니다. 하나는 제품 종류(버튼, 체크박스)이고, 다른 하나는 제품 변형(Windows, macOS)입니다. 먼저 AbstractFactory 인터페이스가 createButton(), createCheckbox() 같은 메서드를 선언합니다. 그리고 각 변형에 맞는 ConcreteFactory(WindowsFactory, MacFactory)가 이 인터페이스를 구현합니다. WindowsFactory.createButton()은 WindowsButton을, MacFactory.createButton()은 MacButton을 돌려줍니다. 클라이언트는 AbstractFactory 타입만 받아서 씁니다. 어떤 ConcreteFactory가 들어오느냐에 따라 만들어지는 객체 전체가 바뀌지만, 클라이언트 코드는 한 줄도 수정할 필요가 없습니다. 이 구조 덕분에 새 변형(Linux)을 추가할 때는 LinuxFactory와 Linux용 제품 클래스만 만들면 됩니다.
팩토리 인터페이스와 구체 팩토리
interface GUIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
}
class WindowsFactory implements GUIFactory {
createButton() { return new WindowsButton(); }
createCheckbox() { return new WindowsCheckbox(); }
}
class MacFactory implements GUIFactory {
createButton() { return new MacButton(); }
createCheckbox() { return new MacCheckbox(); }
}GUIFactory 인터페이스 하나가 제품군 전체의 생성을 정의합니다. 클라이언트가 WindowsFactory를 받으면 Windows 객체만, MacFactory를 받으면 Mac 객체만 나옵니다.
클라이언트 코드
function renderUI(factory: GUIFactory) {
const button = factory.createButton();
const checkbox = factory.createCheckbox();
button.render();
checkbox.render();
}
// 런타임에 팩토리만 바꾸면 전체 제품군이 교체됨
const factory = os === 'windows'
? new WindowsFactory()
: new MacFactory();
renderUI(factory);renderUI 함수는 구체 클래스를 전혀 모릅니다. 분기는 팩토리 선택 시점 단 한 곳에만 존재합니다.
Abstract Factory와 Factory Method는 둘 다 객체 생성을 캡슐화한다는 공통점이 있습니다. 하지만 범위가 다릅니다. Factory Method는 하나의 메서드가 하나의 객체 생성을 서브클래스에 위임하는 패턴이고, Abstract Factory는 관련된 여러 객체를 묶어 제품군 단위로 생성하는 패턴입니다. 판단 기준은 간단합니다. 만들어야 할 객체가 하나이고 변형만 다르다면 Factory Method로 충분합니다. 하지만 버튼과 체크박스처럼 함께 쓰여야 하고 섞이면 안 되는 객체가 여럿이라면 Abstract Factory가 필요합니다. Abstract Factory의 한계도 있습니다. 새로운 제품 종류(예: Slider)를 추가하면 모든 팩토리 인터페이스와 구현체를 수정해야 합니다. 제품 종류가 자주 바뀌는 상황에는 적합하지 않습니다.
Abstract Factory는 같은 애플리케이션이 여러 환경이나 설정에서 동작해야 할 때 자주 등장합니다. 크로스 플랫폼 데스크톱 앱에서 OS별 UI 컴포넌트를 일괄 교체하거나, 데이터 접근 계층에서 DB 벤더별 객체를 팩토리로 전환하는 구조가 대표적입니다. 테스트에서도 유용합니다. 운영 환경에서는 실제 HTTP 클라이언트와 DB 커넥션을 만드는 팩토리를 쓰고, 테스트 환경에서는 목(mock) 객체를 만드는 팩토리로 교체하면 클라이언트 코드를 건드리지 않고 전체 의존성을 바꿀 수 있습니다. 이 패턴을 도입할 신호는 '조건문으로 관련 객체를 묶어 생성하는 코드가 여러 곳에 반복되고, 새 변형이 추가될 때마다 수정 범위가 넓어지는가'입니다. 반대로 제품 종류 자체가 자주 추가되거나, 객체 간 조합 제약이 약한 경우에는 이 패턴의 이점이 줄어듭니다.