추상 팩토리 패턴
1. 개요
1. 개요
추상 팩토리 패턴은 객체 지향 프로그래밍에서 사용되는 생성 패턴 중 하나이다. 이 패턴은 구체적인 클래스를 명시하지 않고도 연관되거나 의존적인 객체들의 패밀리를 생성하기 위한 인터페이스를 제공한다.
패턴의 핵심은 클라이언트 코드가 특정 구현 클래스에 직접 의존하지 않고, 추상 팩토리 인터페이스를 통해 객체를 생성하도록 하는 것이다. 이를 통해 클라이언트는 생성되는 객체의 구체적인 유형을 알 필요가 없으며, 단지 추상 타입에만 의존하게 된다. 결과적으로, 생성할 객체의 패밀리를 변경해야 할 때 클라이언트 코드를 수정하지 않고도 새로운 구체 팩토리를 제공함으로써 쉽게 교체할 수 있다.
이 패턴은 서로 다른 테마나 운영 체제별 GUI 컴포넌트, 다른 데이터베이스 벤더에 맞는 연결 객체, 또는 다양한 제품군을 생성해야 하는 시스템에서 널리 적용된다. 예를 들어, 한 애플리케이션에서 윈도우용 버튼과 스크롤바를 생성하는 팩토리와 맥OS용 버튼과 스크롤바를 생성하는 팩토리를 서로 교체하여 사용할 수 있도록 설계하는 데 유용하다.
추상 팩토리 패턴은 팩토리 메서드 패턴을 활용하여 구현되는 경우가 많지만, 그 의도와 적용 범위는 다르다. 팩토리 메서드 패턴이 단일 제품의 생성을 서브클래스에 위임하는 데 중점을 둔다면, 추상 팩토리 패턴은 관련된 여러 제품 객체들을 함께 생성하는 방법을 정의하는 데 중점을 둔다.
2. 패턴의 의도와 동기
2. 패턴의 의도와 동기
추상 팩토리 패턴은 상호 관련되거나 의존적인 객체들의 집합을 구체적인 클래스를 지정하지 않고 생성할 수 있도록 하는 생성 패턴이다. 이 패턴의 핵심 의도는 클라이언트 코드가 생성할 객체들의 구체적인 클래스에 의존하지 않도록 하여, 시스템을 특정 제품군(Product Family)으로부터 독립시키는 것이다.
패턴의 주요 동기는 클라이언트가 서로 다른 제품군(예: 서로 다른 운영체제의 GUI 컴포넌트, 다른 데이터베이스 벤더의 연결 객체)을 사용해야 할 때 발생한다. 만약 클라이언트 코드가 new 연산자를 통해 구체적인 클래스(예: WindowsButton, MacOSButton)를 직접 생성하면, 새로운 제품군을 지원하기 위해 코드 전체를 수정해야 하는 문제가 생긴다. 추상 팩토리 패턴은 이 문제를 해결하기 위해 각 제품군에 대한 객체 생성을 전담하는 팩토리 객체를 정의하고, 클라이언트는 이 추상적인 팩토리 인터페이스만을 통해 객체를 생성하도록 한다.
따라서 이 패턴은 제품 객체의 생성, 구성, 표현 방식을 시스템의 나머지 부분과 완전히 분리한다. 클라이언트는 추상 인터페이스를 통해서만 제품 객체에 접근하며, 실제로 어떤 구체적인 제품이 생성되는지는 런타임에 결정된다. 이는 시스템의 구성 요소를 쉽게 교체할 수 있게 하여 유연성과 재사용성을 크게 향상시킨다.
3. 구조
3. 구조
추상 팩토리 패턴의 구조는 크게 추상 팩토리, 구체적 팩토리, 추상 제품, 구체적 제품이라는 네 가지 핵심 참여자로 구성된다. 이 패턴은 클라이언트 코드가 구체적인 클래스를 명시하지 않고도 연관된 객체들의 패밀리를 생성할 수 있도록 한다.
참여자(참여 클래스/객체)
주요 참여자와 그 역할은 다음과 같다.
참여자 | 역할 |
|---|---|
추상 팩토리 (AbstractFactory) | 패턴의 핵심 인터페이스로, 생성 메서드들을 선언한다. 각 메서드는 추상 제품 타입을 반환한다. |
구체적 팩토리 (ConcreteFactory) | 추상 팩토리 인터페이스를 구현한다. 특정 제품 패밀리를 생성하는 팩토리 메서드들의 구체적인 구현을 담당한다. |
추상 제품 (AbstractProduct) | 생성될 제품 객체들의 공통 인터페이스를 정의한다. |
구체적 제품 (ConcreteProduct) | 추상 제품 인터페이스를 구현하는 구체적인 제품 객체이다. 특정 구체적 팩토리에 의해 생성되며, 함께 동작하도록 설계된다. |
클라이언트 (Client) |
협력 방법
협력은 일반적으로 다음과 같은 순서로 이루어진다.
1. 클라이언트는 실행 시점에 구체적 팩토리 객체를 하나 생성한다. 이때 추상 팩토리 타입을 사용한다.
2. 클라이언트는 이 팩토리 객체를 사용하여 이후의 모든 제품 생성 요청을 처리한다. 즉, 팩토리의 생성 메서드를 호출한다.
3. 각 생성 메서드는 추상 제품 타입의 객체를 반환한다. 클라이언트는 반환된 제품이 어떤 구체적인 제품인지 알 필요 없이, 추상 제품 인터페이스를 통해 제품을 조작한다.
이 구조의 핵심은 클라이언트가 특정 구체적 팩토리와 구체적 제품에 결합되지 않는다는 점이다. 팩토리 객체를 교체하는 것만으로 전체 제품 패밀리를 쉽게 변경할 수 있다.
3.1. 참여자(참여 클래스/객체)
3.1. 참여자(참여 클래스/객체)
추상 팩토리 패턴의 참여자는 크게 네 가지 주요 역할로 구성된다. 이들은 추상 팩토리, 구체 팩토리, 추상 제품, 구체 제품이다.
첫째, 추상 팩토리는 패턴의 핵심 인터페이스이다. 이 인터페이스는 서로 연관되거나 의존적인 제품 객체들의 집합을 생성하기 위한 연산들을 선언한다. 각 연산은 추상 제품 타입의 객체를 반환한다. 둘째, 구체 팩토리는 추상 팩토리 인터페이스를 구현한다. 이 클래스는 구체 제품 객체를 생성하는 연산을 실제로 구현하며, 특정 제품군에 속하는 객체들을 생성하는 책임을 진다. 예를 들어, 윈도우 팩토리는 윈도우 버튼과 윈도우 스크롤바를 생성한다.
셋째, 추상 제품은 생성될 제품 객체들의 공통 인터페이스를 정의한다. 예를 들어, 버튼이나 스크롤바와 같은 추상 타입이 이에 해당한다. 넷째, 구체 제품은 추상 제품 인터페이스를 구현하는 구체적인 클래스이다. 이들은 구체 팩토리에 의해 생성되며, 특정 구현을 제공한다. 윈도우 버튼과 맥 버튼은 모두 버튼이라는 추상 제품의 구체 제품이다.
참여자 역할 | 설명 | 주요 책임 |
|---|---|---|
제품군 생성 연산을 선언하는 인터페이스 | 제품 생성 메서드의 시그니처 정의 | |
추상 팩토리를 구현하는 클래스 | 특정 제품군의 구체 제품 인스턴스 생성 | |
제품 객체의 공통 인터페이스 | 제품의 공통 연산 정의 | |
추상 제품을 구현하는 클래스 | 특정 제품군에 속하는 실제 객체 구현 |
이러한 참여자들은 클라이언트 코드가 구체 클래스에 직접 의존하지 않고도 연관된 객체들의 제품군을 생성할 수 있도록 협력한다. 클라이언트는 추상 팩토리와 추상 제품 인터페이스만을 통해 객체들과 상호작용한다.
3.2. 협력 방법
3.2. 협력 방법
추상 팩토리 패턴의 협력은 클라이언트, 추상 팩토리, 구체 팩토리, 추상 제품, 구체 제품이라는 다섯 가지 핵심 참여자 간의 명확한 흐름으로 이루어진다.
클라이언트는 추상 팩토리 인터페이스를 통해서만 제품 객체들을 생성한다. 클라이언트는 자신이 사용하는 팩토리가 어떤 구체 팩토리인지 알지 못하며, 생성되는 제품의 구체 클래스도 알지 못한다. 클라이언트는 추상 팩토리에 정의된 팩토리 메서드 패턴을 호출하여 추상 타입의 제품 객체를 얻는다. 예를 들어, createButton() 메서드를 호출하면 추상 버튼 객체가 반환되고, createCheckbox() 메서드를 호출하면 추상 체크박스 객체가 반환된다.
이 과정에서 구체 팩토리 객체는 런타임에 생성되어 클라이언트에 전달된다. 클라이언트는 이 구체 팩토리를 추상 팩토리 타입으로 참조한다. 구체 팩토리는 자신이 생성해야 하는 제품군을 결정하며, 각 팩토리 메서드를 구현하여 해당 제품군에 속하는 구체 제품 객체를 생성하고 반환한다. 예를 들어, Windows 팩토리는 createButton()에서 Windows 버튼 객체를, createCheckbox()에서 Windows 체크박스 객체를 반환한다. 이로 인해 클라이언트가 사용하는 모든 제품 객체는 동일한 테마나 제품군에 속하게 되어 일관성을 보장받는다.
참여자 | 역할 | 협력 순서 |
|---|---|---|
클라이언트 | 팩토리를 사용하여 제품 객체 생성 요청 | 1. 추상 팩토리의 메서드 호출 |
추상 팩토리 | 생성 메서드 인터페이스 정의 | 2. 구체 팩토리에 구현 위임 |
구체 팩토리 | 특정 제품군의 구체 제품 생성 | 3. 새로운 구체 제품 객체 생성 및 반환 |
추상 제품 | 제품 객체의 인터페이스 정의 | 4. 클라이언트가 추상 인터페이스를 통해 제품 사용 |
구체 제품 | 구현된 제품 객체 | - |
결과적으로, 클라이언트는 특정 구현에 종속되지 않고 추상 인터페이스를 통해 제품 객체들을 조작할 수 있다. 새로운 제품군을 지원해야 할 때는 새로운 구체 팩토리 클래스를 구현하고, 런타임에 해당 팩토리 객체를 클라이언트에 설정하기만 하면 된다. 이는 클라이언트 코드의 변경 없이 제품군 전체를 교체할 수 있게 하는 핵심 협력 방식이다.
3.3. UML 다이어그램
3.3. UML 다이어그램
추상 팩토리 패턴의 UML 다이어그램은 패턴의 핵심 구성 요소와 그들 간의 관계를 시각적으로 표현한다. 이 다이어그램은 일반적으로 클래스 다이어그램으로 그려지며, 추상 팩토리, 구체적 팩토리, 추상 제품, 구체적 제품 클래스들 사이의 상속, 합성, 의존 관계를 명확히 보여준다.
다이어그램의 주요 구조는 다음과 같다. 하나의 추상 팩토리 인터페이스 또는 추상 클래스가 존재하며, 이는 여러 제품군을 생성하는 메서드(예: createProductA(), createProductB())를 선언한다. 이 추상 팩토리를 상속받아 구체적인 구현을 제공하는 여러 구체적 팩토리 클래스(예: ConcreteFactory1, ConcreteFactory2)가 있다. 각 구체적 팩토리는 특정 제품군에 속하는 모든 제품 객체들을 생성하는 책임을 진다. 제품 측면에서는 각 제품 유형(예: AbstractProductA, AbstractProductB)에 대한 추상 제품 인터페이스가 정의되고, 이를 구현하는 구체적 제품 클래스들(예: ProductA1/ProductA2, ProductB1/ProductB2)이 존재한다. 클라이언트는 추상 팩토리 타입을 통해 팩토리에 접근하며, 생성된 제품들도 각각의 추상 제품 타입으로 참조된다.
관계 유형 | 연결 대상 | 설명 |
|---|---|---|
상속/구현 |
| 구체적 클래스가 추상 인터페이스를 구현하거나 상속받는다. |
생성(의존) |
| 구체적 팩토리가 자신의 제품군에 속하는 구체적 제품 객체를 생성한다. |
사용(연관) |
| 클라이언트가 추상 팩토리와 추상 제품 인터페이스에만 의존한다. |
이 UML 다이어그램은 클라이언트 코드가 구체적인 클래스 이름을 직접 언급하지 않고도 관련 객체들의 군(family)을 생성할 수 있도록 하는 패턴의 본질, 즉 제품 생성의 책임을 팩토리로 캡슐화하고 제품군 전체를 교체 가능하게 만드는 구조를 한눈에 이해하는 데 도움을 준다.
4. 적용 가능성
4. 적용 가능성
추상 팩토리 패턴은 클라이언트 코드가 생성할 객체들의 구체적인 클래스를 알지 못하도록 하면서, 서로 관련이 있거나 의존적인 객체들의 패밀리를 생성해야 할 때 적용하기 적합하다. 이 패턴은 생성할 객체들의 타입이 사전에 명시되지 않고, 런타임에 결정되는 경우에도 유용하게 사용될 수 있다. 특히 제품군 전체를 일관된 방식으로 교체해야 하는 요구사항이 있을 때 그 효과가 두드러진다.
적합한 상황은 다음과 같다.
시스템이 생성할 객체의 구현 클래스에 독립적이어야 할 때
여러 제품군 중 하나를 구성하는 객체들을 함께 사용해야 할 때
관련된 제품 객체들이 함께 사용되도록 설계되었고, 이 일관성을 유지해야 할 때
제품에 대한 클래스 라이브러리를 제공하고, 그 구현이 아닌 인터페이스를 노출하고 싶을 때
반면, 추상 팩토리 패턴은 새로운 종류의 제품을 기존 팩토리에 추가하기 어렵다는 단점이 있다. 추상 팩토리 인터페이스는 생성 가능한 제품의 종류를 고정시킨다. 따라서 새로운 종류의 제품을 지원하려면 팩토리 인터페이스를 변경하고, 이에 따라 모든 구상 팩토리 클래스를 수정해야 한다. 이는 개방-폐쇄 원칙을 위배하는 행위이다.
부적합한 상황은 다음과 같다.
제품군이 자주 추가되거나 변경될 가능성이 높은 경우
단일한 제품 객체만 생성하면 되는 간단한 경우 (이 경우 팩토리 메서드 패턴이 더 적합할 수 있음)
제품 객체들 간의 강한 결합이 오히려 시스템의 유연성을 해치는 경우
초기 개발 단계에서 제품의 구조가 명확하지 않아 패턴 적용이 과도한 설계가 될 수 있는 경우
적합한 상황 | 부적합한 상황 |
|---|---|
관련 객체들의 패밀리를 함께 생성해야 할 때 | 단일 객체만 생성해도 될 때 |
시스템이 구체적인 클래스에 독립적이어야 할 때 | 제품 종류가 빈번히 변경될 때 |
제품군 전체의 일관된 교체가 필요할 때 | 초기 설계 단계로 과도한 설계가 될 때 |
제품 객체들의 일관된 사용을 보장해야 할 때 | 객체 간 결합이 유연성을 해칠 때 |
4.1. 적합한 상황
4.1. 적합한 상황
추상 팩토리 패턴은 다음과 같은 상황에서 특히 유용하게 적용될 수 있다.
첫째, 시스템이 생성할 객체의 클래스를 명시하지 않고도, 서로 연관되거나 의존적인 객체들의 군을 생성해야 할 때이다. 예를 들어, 사용자 인터페이스(UI) 툴킷에서 윈도우, 버튼, 텍스트 상자 등이 일관된 스타일(예: Material Design, Flat Design)로 함께 생성되어야 하는 경우가 이에 해당한다. 패턴은 이러한 관련 객체 군을 생성하는 인터페이스를 제공하여, 구체적인 클래스를 코드에서 분리한다.
둘째, 여러 제품군 중 하나를 선택하여 시스템을 구성해야 하고, 구성된 제품군을 다른 것으로 대체할 수 있어야 할 때이다. 예를 들어, 애플리케이션이 운영 체제별(Windows, macOS, Linux)로 다른 네이티브 UI 컴포넌트를 사용해야 한다면, 각 OS에 대한 구체적인 팩토리를 구현하고 런타임에 적절한 팩토리를 선택하여 전체 UI 객체 군을 생성할 수 있다. 이는 시스템의 설정이나 환경에 따라 생성되는 객체 군을 유연하게 교체할 수 있게 만든다.
적합한 상황 | 설명 및 예시 |
|---|---|
관련 객체 군의 생성 | 일관된 테마나 개념을 공유하는 객체들을 함께 생성해야 할 때 (예: UI 툴킷, 다른 데이터베이스 벤더에 맞는 연결 객체군). |
제품군의 교체 가능성 | 시스템이 사용하는 제품군 전체를 다른 구현으로 투명하게 교체해야 할 때 (예: 다른 운영 체제 어댑터, 다른 외부 서비스 공급자 세트). |
생성 로직의 캡슐화 | 클라이언트 코드가 객체의 구체적인 클래스에 의존하지 않고, 객체 생성 인터페이스만을 통해 작업할 수 있게 할 때. |
마지막으로, 제품 객체들이 함께 사용되도록 설계되었고, 이 상호 호환성을 보장해야 할 때 추상 팩토리 패턴이 적합하다. 패턴은 특정 구체 팩토리가 생성하는 모든 객체들이 서로 호환되도록 설계하는 것을 강제한다. 따라서 클라이언트는 서로 다른 팩토리에서 생성된 객체들을 혼합하여 사용하는 실수를 방지할 수 있다.
4.2. 부적합한 상황
4.2. 부적합한 상황
추상 팩토리 패턴은 생성할 객체의 군이 자주 변경되거나 확장될 때 유용합니다. 반대로, 생성할 객체의 종류가 고정되어 있고 변경될 가능성이 거의 없는 경우에는 이 패턴을 적용하는 것이 오히려 복잡성만 증가시킬 수 있습니다. 패턴을 도입하면 새로운 인터페이스와 클래스 계층이 생겨 코드베이스가 커지므로, 단순한 팩토리 메서드 패턴이나 직접적인 객체 생성으로 충분히 해결할 수 있는 문제에는 과도한 설계가 될 수 있습니다.
또한, 제품군을 구성하는 개별 제품들 사이에 실제로 연관성이나 일관성이 존재하지 않는 경우에도 추상 팩토리 패턴은 적합하지 않습니다. 이 패턴은 서로 다른 제품들이 함께 동작해야 하는 경우를 전제로 하기 때문입니다. 예를 들어, 서로 완전히 독립적인 GUI 버튼과 데이터베이스 연결 객체를 하나의 팩토리에서 생성해야 한다면, 이는 패턴의 본래 의도와 맞지 않습니다.
초기 개발 단계에서 요구사항이 명확하지 않거나 프로토타입을 빠르게 만들어야 할 때도 이 패턴의 도입을 지연하는 것이 좋습니다. 패턴의 구조를 설계하고 구현하는 데 드는 비용이 예상된 유연성의 이점보다 클 수 있기 때문입니다. 시스템이 매우 작거나 단순하여 제품군의 개념 자체가 필요하지 않은 경우에도 적용이 부적합합니다.
부적합한 상황 | 주요 이유 |
|---|---|
객체의 종류가 고정적이고 변하지 않음 | 불필요한 추상화로 인한 복잡성 증가 |
제품들 간에 논리적 연관성이 없음 | 패턴의 전제 조건(일관된 제품군)을 충족하지 못함 |
빠른 프로토타이핑이 필요한 초기 단계 | 구현 및 설계 비용 대비 효용이 낮음 |
시스템 규모가 매우 작고 단순함 | 오버엔지니어링[1]으로 이어질 수 있음 |
5. 장단점
5. 장단점
추상 팩토리 패턴의 주요 장점은 제품군 전체의 일관성을 보장한다는 점이다. 클라이언트 코드는 특정 구상 팩토리를 통해 생성되는 모든 제품들이 함께 동작하도록 설계되었다는 것을 신뢰할 수 있다. 이는 서로 다른 제품군(예: Windows 위젯군 vs macOS 위젯군) 간의 호환성 문제를 방지한다. 또한, 구체적인 제품 클래스들을 클라이언트 코드로부터 완전히 분리시켜, 새로운 제품군이나 제품 변형을 시스템에 추가하기 쉽게 만든다. 클라이언트는 추상 인터페이스만을 통해 객체들과 협력하므로, 팩토리를 교체하는 것만으로 전체 제품군을 쉽게 변경할 수 있다.
이 패턴의 단점은 새로운 종류의 제품을 기존 제품군에 추가하기가 어렵다는 것이다. 예를 들어, 기존의 GUI 팩토리가 버튼과 체크박스만 생성한다고 가정할 때, 새로운 제품인 스크롤바를 지원하려면 추상 팩토리 인터페이스와 이 인터페이스를 구현하는 모든 구상 팩토리 클래스를 수정해야 한다. 이는 인터페이스 확장에 따른 광범위한 코드 변경을 유발한다. 또한, 패턴 구현을 위해 많은 수의 새로운 클래스와 인터페이스가 도입되어 코드 복잡성이 증가할 수 있다. 단순한 제품 생성의 경우, 이 패턴은 과도한 설계가 될 위험이 있다.
장점 | 단점 |
|---|---|
제품군의 일관성을 보장함 | 새로운 종류의 제품 추가가 어려움 |
구체적인 클래스들을 클라이언트에서 분리함 | 많은 인터페이스와 클래스가 생겨 복잡도가 증가할 수 있음 |
제품군을 쉽게 교체할 수 있음 | 단순한 객체 생성에는 과도한 설계가 될 수 있음 |
단일 책임 원칙을 준수하며 생성 코드를 한 곳으로 집중시킴 | 클라이언트 코드가 추상 팩토리 인터페이스에 강하게 결합될 수 있음 |
요약하면, 이 패턴은 관련 객체들의 군을 생성해야 하고, 이들이 함께 사용되거나 교체 가능해야 하는 복잡한 시스템에서 강력한 이점을 제공한다. 그러나 유연성 확보와 복잡도 증가 사이의 트레이드오프를 고려하여 적용해야 한다.
5.1. 장점
5.1. 장점
추상 팩토리 패턴의 주요 장점은 구체적인 클래스의 인스턴스화를 분리하여 시스템의 유연성과 일관성을 높이는 데 있다. 첫째, 제품군을 생성하는 인터페이스를 제공함으로써 클라이언트 코드가 구체적인 클래스에 의존하지 않게 한다. 클라이언트는 추상 제품 인터페이스를 통해 객체를 조작하므로, 실제로 생성되는 구체적인 제품군(예: Windows 위젯군 vs. macOS 위젯군)이 변경되어도 코드 수정 없이 교체할 수 있다. 이는 의존성 역전 원칙을 따르며, 시스템을 특정 구현으로부터 독립시킨다.
둘째, 제품군 내의 객체들이 함께 동작하도록 보장하여 일관성을 유지한다. 패턴은 상호 관련되거나 의존적인 객체들을 함께 생성하도록 설계되었기 때문에, 서로 호환되지 않는 제품(예: Windows 버튼과 macOS 스크롤바)이 조합되는 것을 방지한다. 이는 잘못된 객체 조합으로 인한 런타임 오류를 사전에 차단하는 효과가 있다.
또한, 새로운 종류의 제품군을 시스템에 추가하는 것이 비교적 용이하다. 새로운 구체 팩토리 클래스를 구현하고, 필요한 새로운 구체 제품 클래스들을 생성하기만 하면 된다. 기존의 클라이언트 코드는 변경할 필요가 없으며, 단지 새로운 팩토리 인스턴스를 생성자나 설정 메서드를 통해 주입받으면 된다. 이는 개방-폐쇄 원칙을 지원하는 형태이다.
마지막으로, 객체 생성 코드를 한 곳(팩토리 클래스들)에 집중시켜 유지보수성을 향상시킨다. 제품 객체 생성 방식을 변경해야 할 때, 관련 코드를 여러 클라이언트에서 찾아 수정하는 대신, 해당 구체 팩토리 클래스만 수정하면 된다. 이는 코드 중복을 줄이고 변경의 영향을 국소화한다.
5.2. 단점
5.2. 단점
추상 팩토리 패턴은 새로운 제품군을 추가하기 어렵다. 새로운 종류의 제품을 지원하려면 추상 팩토리 인터페이스를 변경해야 하며, 이는 모든 구체 팩토리 클래스와 이를 사용하는 클라이언트 코드에 영향을 미친다. 이는 개방-폐쇄 원칙을 위반하는 것으로, 확장에는 열려 있지만 변경에는 닫혀 있어야 하는 객체지향 설계 원칙과 충돌한다.
패턴의 구현 복잡성이 증가한다. 관련 객체들을 생성하는 인터페이스를 제공하기 위해 많은 수의 새로운 인터페이스와 클래스들이 도입된다. 이는 단순한 객체 생성 작업에 비해 코드베이스의 규모와 구조적 복잡성을 크게 늘린다. 특히 작은 규모의 애플리케이션이나 제품군의 종류가 고정된 경우에는 과도한 설계가 될 수 있다.
구체 팩토리 객체가 생성하는 구체 제품 클래스들을 클라이언트가 미리 알고 있어야 한다. 클라이언트는 팩토리 메서드 패턴을 통해 제품을 생성하지만, 어떤 구체 클래스의 인스턴스가 반환되는지에 대한 지식이 암묵적으로 필요하다. 이는 제품 객체의 구체적인 타입에 대한 의존성을 완전히 제거하지 못함을 의미한다.
단점 | 설명 |
|---|---|
제품군 확장의 어려움 | 인터페이스 변경이 필요하여 기존 코드 수정을 유발한다. |
복잡성 증가 | 많은 인터페이스와 클래스로 인해 구조가 복잡해진다. |
구체 클래스에 대한 암묵적 의존 | 클라이언트가 반환될 구체 제품 타입을 간접적으로 알아야 한다. |
6. 구현 고려사항 및 예제
6. 구현 고려사항 및 예제
구현 시에는 먼저 제품군을 구성하는 추상 제품 인터페이스나 추상 클래스를 정의한다. 예를 들어, GUI 구성 요소를 만드는 팩토리라면 Button, Checkbox 같은 추상 제품을 선언한다. 이후 각 추상 제품에 대한 구체적인 제품 클래스들을 구현한다. 마지막으로, 이 제품들을 생성하는 추상 팩토리 인터페이스를 선언하고, 각 제품군별로 이를 구현하는 구체 팩토리 클래스를 작성한다. 클라이언트 코드는 구체 팩토리의 인스턴스를 생성 시점에 결정하고, 이후에는 추상 팩토리 인터페이스를 통해서만 제품을 생성한다.
다양한 프로그래밍 언어에서의 구현 방식은 다음과 같은 특징을 보인다.
언어 | 주요 구현 특징 | 간단한 예시 구성 요소 |
|---|---|---|
인터페이스와 클래스를 명시적으로 사용. |
| |
추상 기본 클래스와 가상 함수 활용. 메모리 관리(예: 스마트 포인터)를 고려해야 함. |
| |
동적 타입 특성을 활용. 종종 간단한 함수나 클래스를 팩토리로 사용할 수 있음. |
|
구현 시 고려할 점은 구체 팩토리를 추가하거나 제품군을 확장하는 것이 비교적 쉽다는 것이다. 새로운 스타일의 UI 제품군을 추가하려면 해당 제품군에 맞는 구체 팩토리 클래스와 구체 제품 클래스들만 새로 구현하면 된다. 그러나 새로운 종류의 제품(예: 기존 Button, Checkbox에 Slider를 추가)을 추상 팩토리에 포함시키는 것은 기존의 모든 구체 팩토리 클래스를 수정해야 하므로 어렵다. 이는 패턴의 주요 한계점 중 하나이다.
6.1. 구현 단계
6.1. 구현 단계
추상 팩토리 패턴을 구현하는 일반적인 단계는 다음과 같다. 먼저, 생성할 제품군의 공통 인터페이스 또는 추상 클래스를 정의한다. 예를 들어, '버튼'과 '체크박스'라는 서로 다른 제품군이 있다면, Button과 CheckBox라는 추상 타입을 각각 만든다.
다음으로, 각 구체적인 제품군(예: Windows 스타일, macOS 스타일)에 대해 위에서 정의한 추상 타입들을 구현한 구체 클래스들을 생성한다. 예를 들어 WindowsButton, MacButton, WindowsCheckBox, MacCheckBox 클래스들을 만든다. 이후, 이 제품군들을 생성할 추상 팩토리 인터페이스를 정의한다. 이 인터페이스는 각 제품 타입별 생성 메서드(예: createButton(), createCheckBox())를 선언한다.
마지막으로, 각 구체적인 제품군에 해당하는 구체 팩토리 클래스를 구현한다. WindowsFactory와 MacFactory 같은 클래스가 추상 팩토리 인터페이스를 구현하여, 자신의 스타일에 맞는 구체 제품 객체(예: WindowsButton 객체)를 반환하도록 각 생성 메서드를 작성한다. 클라이언트 코드는 구체 팩토리 타입에 의존하지 않고 추상 팩토리 인터페이스만을 통해 제품 객체들을 생성하고 사용한다.
구현 시 고려할 점은 제품군이 추가되거나 변경될 때의 확장성이다. 새로운 스타일(예: Linux 스타일)의 제품군을 추가하려면, 새로운 구체 제품 클래스들과 이를 생성할 새로운 구체 팩토리 클래스 하나만 구현하면 된다. 이는 개방-폐쇄 원칙을 따르는 방식이다. 그러나 새로운 종류의 제품(예: '스크롤바')이 제품군에 추가된다면, 추상 팩토리 인터페이스와 모든 구체 팩토리 클래스를 수정해야 하므로 변경이 어려워진다.
6.2. 언어별 예제 (Java, C++, Python 등)
6.2. 언어별 예제 (Java, C++, Python 등)
구현 예제는 객체 지향 프로그래밍 언어의 문법적 특성에 따라 다르게 나타난다. 공통적인 개념은 추상 팩토리 인터페이스와 이를 구현하는 구체 팩토리 클래스, 그리고 생성될 제품군에 대한 추상 클래스 또는 인터페이스로 구성된다.
다음은 주요 언어별 구현 예제의 개요이다.
언어 | 핵심 구성 요소 | 특징 |
|---|---|---|
| 인터페이스를 명시적으로 정의하여 제품군의 생성을 추상화한다. | |
| 가상 함수를 가진 추상 기본 클래스를 사용하며, 메모리 할당( | |
| 공식적인 인터페이스 문법이 없어, 덕 타이핑[2]에 의존한다. 구체 팩토리는 단순히 특정 제품 객체를 반환하는 메서드를 가진다. |
Java 예제에서는 GUIFactory 인터페이스에 createButton()과 createCheckbox() 메서드를 선언한다. WinFactory와 MacFactory 클래스가 이를 구현하여 각각 Windows 스타일과 macOS 스타일의 Button 및 Checkbox 객체를 반환한다. 클라이언트 코드는 특정 팩토리 인스턴스에 의존하지 않고 GUIFactory 인터페이스를 통해 제품을 생성한다.
C++ 예제에서는 유사한 구조를 가지지만, 생성된 제품 객체에 대한 포인터를 반환하며, 사용 후 메모리 해제를 신경 써야 한다. 현대 C++에서는 std::unique_ptr 같은 스마트 포인터를 활용하여 자원 관리 부담을 줄일 수 있다.
Python 예제는 더 간결하다. AbstractFactory 클래스를 정의하거나, 단순히 공통 메서드 시그니처를 기대하는 방식으로 구현한다. ConcreteFactory1 클래스는 create_product_a() 메서드에서 ConcreteProductA1 객체를 반환한다. Python의 동적 특성으로 인해, 제품 객체들이 공통의 베이스 클래스를 명시적으로 상속받지 않아도 기능상 문제가 없을 수 있다.
7. 관련 패턴
7. 관련 패턴
추상 팩토리 패턴은 팩토리 메서드 패턴과 밀접한 관련이 있다. 추상 팩토리 패턴은 종종 팩토리 메서드 패턴을 활용하여 구현된다. 즉, 추상 팩토리 클래스 내의 각 팩토리 메서드가 구체적인 제품 객체를 생성하는 책임을 진다. 두 패턴 모두 객체 생성 로직을 캡슐화하여 클라이언트 코드와 구체 클래스의 결합을 낮추는 공통 목표를 공유하지만, 초점이 다르다. 팩토리 메서드 패턴은 단일 제품의 생성을 서브클래스에 위임하는 반면, 추상 팩토리 패턴은 연관되거나 의존적인 제품군 전체를 생성하는 인터페이스를 제공한다.
빌더 패턴과도 유사점과 차이점이 존재한다. 두 패턴 모두 복잡한 객체 생성 과정을 캡슐화한다. 그러나 빌더 패턴은 단일 복합 객체를 단계별로 구성하는 데 중점을 두는 반면, 추상 팩토리 패턴은 서로 연관된 여러 제품 객체들의 군을 생성하는 데 중점을 둔다. 빌더 패턴은 최종 결과물을 반환하기 전에 생성 과정의 여러 단계를 거칠 수 있지만, 추상 팩토리 패턴은 생성된 제품을 즉시 반환한다.
프로토타입 패턴은 추상 팩토리 패턴과 함께 사용될 수 있다. 추상 팩토리 클래스가 특정 프로토타입 객체를 복제(clone)하여 새로운 제품을 생성하는 방식으로 구현될 수 있다[3]. 이 경우 팩토리는 구체적인 클래스를 명시적으로 참조하지 않고도 새로운 인스턴스를 생성할 수 있다. 이는 시스템이 런타임에 제품군을 추가하거나 변경해야 할 때 특히 유용한 접근 방식이다.
관련 패턴 | 관계 | 주요 차이점 |
|---|---|---|
협력/구현 수단 | 팩토리 메서드는 단일 객체 생성, 추상 팩토리는 객체 군 생성. | |
대안/보완 | 빌더는 복합 객체의 단계적 구성, 추상 팩토리는 관련 객체 군의 즉시 생성. | |
협력/구현 수단 | 프로토타입은 복제를 통한 생성, 추상 팩토리는 이 방식을 활용할 수 있음. |
7.1. 팩토리 메서드 패턴
7.1. 팩토리 메서드 패턴
팩토리 메서드 패턴은 객체 생성을 서브클래스에 위임하는 생성 패턴이다. 이 패턴은 슈퍼클래스에서 객체를 생성하기 위한 인터페이스를 정의하지만, 생성할 객체의 정확한 클래스를 결정하는 것은 서브클래스에 맡긴다. 따라서 클라이언트 코드는 생성될 구체적인 클래스에 대해 알 필요 없이, 공통 인터페이스를 통해 객체를 생성하고 사용할 수 있다.
추상 팩토리 패턴과의 가장 큰 차이는 복잡성과 범위에 있다. 팩토리 메서드 패턴은 단일 제품의 생성을 다루는 반면, 추상 팩토리 패턴은 연관된 또는 의존적인 제품군 전체를 생성하는 방법을 정의한다. 추상 팩토리는 종종 여러 개의 팩토리 메서드로 구현된다. 즉, 추상 팩토리 패턴은 팩토리 메서드 패턴을 구성 요소로 활용하여 더 높은 수준의 추상화를 제공한다.
두 패턴의 관계는 다음 표로 정리할 수 있다.
패턴 | 초점 | 구조 |
|---|---|---|
단일 제품의 생성 | 하나의 메서드를 통해 하나의 객체 생성 | |
제품군의 생성 | 여러 메서드(팩토리 메서드들)를 묶은 인터페이스를 통해 관련 객체들을 생성 |
팩토리 메서드 패턴은 클래스 상속에 의존하는 반면, 추상 팩토리 패턴은 객체 합성을 강조한다. 이는 디자인 패턴의 핵심 원칙 중 하나인 "상속보다는 합성을 선호하라"는 측면에서 추상 팩토리가 더 유연한 구조를 가질 수 있음을 시사한다. 그러나 단일 제품 생성이라는 단순한 요구사항에는 팩토리 메서드 패턴이 더 적합하고 직관적인 해결책이 된다.
7.2. 빌더 패턴
7.2. 빌더 패턴
빌더 패턴은 복잡한 객체의 생성 과정을 단계별로 분리하여, 동일한 생성 절차에서 서로 다른 표현(구성)을 가진 객체를 만들 수 있게 하는 생성 패턴이다. 이 패턴은 객체의 생성 코드와 표현 코드를 분리하여, 생성 알고리즘은 독립적으로 유지하면서 객체의 내부 표현을 다양하게 변경할 수 있게 한다.
주로 생성해야 할 객체가 많은 선택적 매개변수를 가지거나, 생성 단계에 순서가 존재할 때 적용된다. 예를 들어, HTML 문서 생성기나 복잡한 의식적 인터페이스를 구성하는 GUI 컴포넌트를 만드는 경우에 유용하다. 패턴의 주요 참여자로는 제품을 정의하는 Product, 생성 단계를 선언하는 Builder 인터페이스, 구체적인 생성 단계를 구현하는 ConcreteBuilder, 그리고 생성 과정을 지시하는 Director가 있다.
빌더 패턴과 추상 팩토리 패턴은 모두 복잡한 객체 생성을 다루지만 목적이 다르다. 추상 팩토리 패턴은 연관된 객체 군을 생성하는 데 중점을 두는 반면, 빌더 패턴은 하나의 복합 객체를 단계별로 구성하는 데 중점을 둔다. 또한, 팩토리 메서드 패턴은 서브클래스가 생성할 객체의 클래스를 결정하는 반면, 빌더 패턴은 객체의 복잡한 조립 과정을 추상화한다.
비교 요소 | ||
|---|---|---|
주요 목적 | 복잡한 객체의 단계별 생성 | 연관된 객체 군(제품군)의 생성 |
생성 대상 | 하나의 복합 객체 | 여러 개의 관련 객체 |
생성 방식 | 단계별 조립을 통한 생성 | 구체적인 팩토리가 객체를 즉시 반환 |
적합한 상황 | 객체에 많은 선택적 매개변수나 구성 단계가 있을 때 | 서로 의존하는 객체들을 함께 생성해야 할 때 |
빌더 패턴의 주요 장점은 생성 코드와 표현 코드의 분리, 생성 과정의 세밀한 제어, 그리고 불변성 객체를 만들기 쉽다는 점이다. 단점은 패턴을 적용하기 위해 여러 새로운 클래스(Builder, ConcreteBuilder, Director)를 만들어야 하므로 코드 복잡도가 증가할 수 있다는 점이다.
7.3. 프로토타입 패턴
7.3. 프로토타입 패턴
프로토타입 패턴은 생성 디자인 패턴의 하나로, 새로운 객체를 생성할 때 기존 객체를 복제하는 방식을 사용한다. 이 패턴은 추상 팩토리 패턴이나 팩토리 메서드 패턴과 마찬가지로 객체 생성 과정을 캡슐화하지만, 그 메커니즘이 근본적으로 다르다. 프로토타입 패턴은 특정 클래스에 의존하지 않고, 복제 가능한 프로토타입 인스턴스를 등록해두고 필요할 때 이를 복사하여 새로운 인스턴스를 만든다.
이 패턴의 핵심은 clone 또는 copy 메서드를 정의하는 프로토타입 인터페이스를 두는 것이다. 구체적인 클래스는 이 인터페이스를 구현하여 자신의 복제 로직을 제공한다. 클라이언트는 원하는 객체의 구체적인 클래스를 알 필요 없이, 등록된 프로토타입 객체로부터 복제를 요청하기만 하면 된다. 이 방식은 특히 생성 비용이 큰 객체(예: 데이터베이스에서 복잡한 쿼리 결과를 로드한 객체)나 구성이 매우 복잡한 객체를 생성할 때 유리하다.
패턴 이름 | 생성 방식 | 주요 목적 |
|---|---|---|
관련 객체 군을 생성하는 팩토리 메서드 제공 | 구체적인 클래스에 의존하지 않고 관련 객체들을 생성 | |
서브클래스가 인스턴스화할 클래스를 결정 | 객체 생성을 서브클래스에 위임 | |
프로토타입 패턴 | 기존 인스턴스를 복제 | 새 인스턴스를 생성하는 비용을 줄이고 동적 구성을 가능하게 함 |
추상 팩토리 패턴과 프로토타입 패턴은 함께 사용될 수 있다. 추상 팩토리의 구체적인 팩토리가 프로토타입 객체 컬렉션을 관리하고, 새로운 제품을 생성할 때 이 프로토타입들을 복제하여 반환하는 방식이다[4]. 이렇게 하면 팩토리마다 새로운 서브클래스를 만들 필요 없이, 단순히 다른 프로토타입 객체를 구성함으로써 새로운 제품 군을 쉽게 정의할 수 있다.
8. 실제 적용 사례
8. 실제 적용 사례
추상 팩토리 패턴은 다양한 소프트웨어 프레임워크와 라이브러리에서 플랫폼 독립성을 보장하거나 일관된 테마나 스타일을 제공하기 위해 널리 활용된다. 한 대표적인 예는 그래픽 사용자 인터페이스(GUI) 툴킷이다. 자바의 AWT와 스윙(Swing) 라이브러리, 또는 Qt 프레임워크는 운영체제별로 다른 네이티브 위젯(예: 버튼, 체크박스, 창)을 생성해야 한다. 이때 추상 팩토리 패턴을 사용하면 윈도우용 팩토리, 맥OS용 팩토리, 리눅스용 팩토리를 정의하고, 클라이언트 코드는 특정 팩토리에 의존하지 않고 일관된 인터페이스로 위젯을 생성할 수 있다. 이를 통해 애플리케이션은 하나의 코드베이스를 유지하면서도 각 플랫폼의 고유한 룩앤필을 제공한다.
데이터베이스 접근 계층에서도 이 패턴이 빈번히 적용된다. 애플리케이션이 MySQL, PostgreSQL, 오라클 데이터베이스 등 여러 관계형 데이터베이스 관리 시스템(RDBMS)을 지원해야 할 때, 각 데이터베이스별로 연결(Connection), 명령(Command), 데이터 리더(DataReader) 객체의 생성 방식이 다르다. 추상 팩토리 패턴은 각 데이터베이스 벤더에 맞는 구체적인 팩토리 클래스를 만들어 표준화된 인터페이스로 이러한 객체들을 생성하게 한다. 결과적으로 비즈니스 로직은 특정 데이터베이스 구현에 결합되지 않고, 단순히 팩토리를 교체하는 것만으로 다른 데이터베이스 시스템을 지원할 수 있게 된다.
적용 분야 | 핵심 목적 | 생성 대상 객체 예시 |
|---|---|---|
GUI 툴킷 | 플랫폼 독립성 & 테마 일관성 | |
데이터 접근 계층 | 데이터베이스 독립성 | |
게임 개발 | 게임 엔진별 또는 스타일별 자원 관리 | |
문서 처리 시스템 | 다양한 문서 형식 지원 |
게임 개발 분야에서는 서로 다른 게임 엔진이나 아트 스타일(예: 판타지 vs SF)에 맞는 캐릭터, 배경, 효과음 등의 게임 자원(에셋)을 생성할 때 이 패턴이 유용하다. 예를 들어, '환경'이라는 추상 제품을 정의하고, 이를 '숲 환경', '사막 환경', '우주 환경' 등으로 구체화할 수 있다. 각 환경별 팩토리는 서로 조화를 이루는 배경 텍스처, 적 캐릭터 모델, 환경 음악 객체 세트를 생성하는 책임을 진다. 이렇게 하면 새로운 테마나 엔진을 추가하더라도 기존 객체 생성 코드를 수정하지 않고 새로운 팩토리 클래스만 구현하면 된다.
9. 여담
9. 여담
추상 팩토리 패턴은 종종 팩토리 메서드 패턴과 혼동되곤 한다. 두 패턴 모두 객체 생성을 캡슐화한다는 공통점을 지니지만, 의도와 적용 수준에서 차이가 있다. 팩토리 메서드 패턴은 단일 제품의 생성을 서브클래스에 위임하는 반면, 추상 팩토리 패턴은 연관되거나 의존적인 제품 *군*을 생성하기 위한 인터페이스를 제공한다. 즉, 추상 팩토리는 여러 팩토리 메서드들을 모아놓은 것이라고 볼 수 있다.
이 패턴은 특히 GUI 라이브러리나 크로스 플랫폼 애플리케이션 프레임워크에서 그 유용성이 두드러진다. 예를 들어, 운영체제별로 다른 모양의 버튼(윈도우 버튼, 맥 버튼)과 스크롤바를 생성해야 할 때, 각 OS별로 구체적인 팩토리를 구현하면 클라이언트 코드는 단일 인터페이스만으로 일관된 방식으로 위젯들을 생성할 수 있다. 이는 애플리케이션의 유지보수성과 확장성을 크게 향상시킨다.
패턴의 이름에 '추상'이 붙은 이유는 클라이언트가 구체적인 구현 클래스가 아닌 추상 인터페이스에만 의존하기 때문이다. 이로 인해 새로운 제품군을 추가하는 것은 비교적 쉬운 반면(새로운 구체 팩토리 클래스를 구현하면 됨), 기존 제품군에 새로운 종류의 제품(예: 기존 GUI 팩토리에 체크박스 생성 메서드 추가)을 추가하는 것은 모든 구체 팩토리 클래스의 인터페이스를 변경해야 하므로 어려울 수 있다. 이는 패턴의 주요 한계점으로 지적된다.
