Abstract Factory
Abstract Factory는 구체적인 클래스를 지정하지 않고, 서로 관련 있는 객체들의 제품군(family)을 한꺼번에 생성할 수 있게 해주는 생성 패턴입니다. 클라이언트 코드는 팩토리 인터페이스에만 의존하므로, 어떤 제품군이 만들어지는지는 런타임에 결정됩니다.
▶아키텍처 다이어그램
🔍 구조 다이어그램점선 애니메이션은 데이터 또는 요청의 흐름 방향을 나타냅니다
UI 툴킷을 만든다고 가정합니다. 버튼, 체크박스, 텍스트 필드가 있는데, Windows용과 macOS용이 각각 필요합니다. 이걸 조건문으로 처리하면 `if (os === 'windows') new WindowsButton()` 같은 분기가 컴포넌트마다 반복됩니다. 여기에 Linux가 추가되면 모든 분기를 다시 찾아서 고쳐야 합니다. 더 심각한 문제는 일관성입니다. 실수로 Windows 버튼 옆에 macOS 체크박스가 섞이면 동작이 어긋나거나 렌더링이 깨질 수 있습니다. 관련 객체들이 항상 같은 변형(variant)에서 나와야 한다는 보장이 코드 구조에 없기 때문입니다.
객체 지향 프로그래밍이 보급되면서 new 키워드로 직접 객체를 만드는 코드가 곳곳에 퍼졌습니다. 프로그램이 작을 때는 문제가 안 됐지만, 프로젝트 규모가 커지고 지원 플랫폼이 늘어나면서 생성 로직이 비즈니스 로직 안에 섞이는 상황이 반복됐습니다. GoF(Gang of Four)가 1994년에 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) 객체를 만드는 팩토리로 교체하면 클라이언트 코드를 건드리지 않고 전체 의존성을 바꿀 수 있습니다. 이 패턴을 도입할 신호는 '조건문으로 관련 객체를 묶어 생성하는 코드가 여러 곳에 반복되고, 새 변형이 추가될 때마다 수정 범위가 넓어지는가'입니다. 반대로 제품 종류 자체가 자주 추가되거나, 객체 간 조합 제약이 약한 경우에는 이 패턴의 이점이 줄어듭니다.