Autumn SALE

추상 팩토리 패턴

다음 이름으로도 불립니다: Abstract Factory

의도

추상 팩토리는 관련 객체들의 구상 클래스들을 지정하지 않고도 관련 객체들의 모음을 생성할 수 있도록 하는 생성패턴입니다.

추상 팩토리 패턴

문제

예를 들어 당신이 가구 판매장을 위한 프로그램을 만들고 있다고 가정합시다. 당신의 코드는 다음을 나타내는 클래스들로 구성됩니다:

  1. 관련 제품들로 형성된 패밀리​(제품군), 예: Chair​(의자) + Sofa​(소파) + Coffee­Table​(커피 테이블).

  2. 해당 제품군의 여러 가지 변형. 예를 들어 Chair​(의자) + Sofa​(소파) + Coffee­Table​(커피 테이블) 같은 제품들은 Modern​(현대식), Victorian​(빅토리안), Art­Deco​(아르데코 양식)​와 같은 변형으로 제공됩니다.

제품 패밀리들과 그들의 변형들

제품 패밀리들과 그들의 변형들

이제 당신이 새로운 개별 가구 객체를 생성했을 때, 이 객체들이 기존의 같은 패밀리 내에 있는 다른 가구 객체들과 일치하는 변형​(스타일)​을 가지도록 할 방법이 필요합니다. 그 이유는 당신의 고객이 스타일이 일치하지 않는 가구 세트를 받으면 크게 실망할 수 있기 때문입니다.

현대식 스타일의 소파는 빅토리안 스타일의 의자들과 잘 어울리지 않습니다.

또, 가구 공급업체들은 카탈로그를 매우 자주 변경하기 때문에, 그들은 새로운 제품 또는 제품군​(패밀리)​을 추가할 때마다 기존 코드를 변경해야 하는 번거로움을 피하고 싶을 것입니다.

해결책

추상 공장 패턴의 첫 번째 방안은 각 제품 패밀리​(제품군)​에 해당하는 개별적인 인터페이스를 명시적으로 선언하는 것입니다. (예: 의자, 소파 또는 커피 테이블). 그다음, 제품의 모든 변형이 위 인터페이스를 따르도록 합니다. 예를 들어, 모든 의자의 변형들은 Chair​(의자) 인터페이스를 구현한다; 모든 커피 테이블 변형들은 Coffee­Table​(커피 테이블) 인터페이스를 구현한다, 등의 규칙을 명시합니다.

Chairs 클래스 계층

같은 객체의 모든 변형은 단일 클래스 계층구조로 옮겨져야 합니다.

추상 공장 패턴의 다음 단계는 을 선언하는 것입니다. 추상 공장 패턴은 제품 패밀리 내의 모든 개별 제품들의 생성 메서드들이 목록화되어있는 인터페이스입니다. (예: create­Chair​(의자 생성), create­Sofa​(소파 생성), create­Coffee­Table​(커피 테이블 생성) 등).

_Factories_ 클래스 계층

각 구상 팩토리는 특정 제품의 변형에 해당합니다.

다음은 제품 변형을 다룰 차례입니다. 제품 패밀리의 각 변형에 대해 Abstract­Factory 추상 팩토리 인터페이스를 기반으로 별도의 팩토리 클래스를 생성합니다. 팩토리는 특정 종류의 제품을 반환하는 클래스입니다. 예를 들어 Modern­Furniture­Factory​(현대식 가구 팩토리)​에서는 다음 객체들만 생성할 수 있습니다: Modern­Chair​(현대식 의자), Modern­Sofa​(현대식 소파) 및 Modern­Coffee­Table​(현대식 커피 테이블).

클라이언트 코드는 자신에 해당하는 추상 인터페이스를 통해 팩토리들과 제품들 모두와 함께 작동해야 합니다. 그래야 클라이언트 코드에 넘기는 팩토리의 종류와 제품 변형들을 클라이언트 코드를 손상하지 않으며 자유자재로 변경할 수 있습니다.

클라이언트들은 함께 작업하는 팩토리의 구상 클래스에 대해 신경을 쓰지 않아도 돼야 합니다.

클라이언트가 팩토리에 의자를 주문했다고 가정해 봅시다. 클라이언트는 팩토리의 클래스들을 알 필요가 없으며, 팩토리가 어떤 변형의 의자를 생성할지 전혀 신경을 쓰지 않습니다. 클라이언트는 추상 Chair​(의자) 인터페이스를 사용하여, 현대식 의자이든 빅토리아식 의자이든 종류에 상관없이 모든 의자를 항상 동일한 방식으로 주문하며, 그가 의자에 대해 아는 유일한 사실은 제품이 sit­On​(앉을 수 있다) 메서드를 구현한다는 것뿐입니다. 그러나, 생성된 의자의 변형은 항상 같은 팩토리 객체에서 생성된 소파 또는 커피 테이블의 변형과 같을 것입니다.

여기에서 명확히 짚고 넘어가야 할 점이 있습니다. 클라이언트가 추상 인터페이스에만 노출된다면 실제 팩토리 객체를 생성하는 것은 무엇일까요? 일반적으로 프로그램은 초기화 단계에서 구상 팩토리 객체를 생성합니다. 그 직전에 프로그램은 환경 또는 구성 설정에 따라 팩토리 유형을 선택해야 합니다.

구조

추상 팩토리 디자인 패턴추상 팩토리 디자인 패턴
  1. 추상 제품들은 제품 패밀리를 구성하는 개별 연관 제품들의 집합에 대한 인터페이스들을 선언합니다.

  2. 구상 제품들은 변형들로 그룹화된 추상 제품들의 다양한 구현들입니다. 각 추상 제품​(의자/소파)​은 주어진 모든 변형​(빅토리안/현대식)​에 구현되어야 합니다.

  3. 추상 팩토리 인터페이스는 각각의 추상 제품들을 생성하기 위한 여러 메서드들의 집합을 선언합니다.

  4. 구상 팩토리들은 추상 팩토리의 생성 메서드들을 구현합니다. 각 구상 팩토리는 제품들의 특정 변형들에 해당하며 해당 특정 변형들만 생성합니다.

  5. 구상 팩토리들은 구상 제품들을 인스턴스화하나, 그 제품들의 생성 메서드들의 시그니처들은 그에 해당하는 제품들을 반환해야 합니다. 그래야 팩토리를 사용하는 클라이언트 코드가 팩토리에서 받은 제품의 특정 변형과 결합되지 않습니다. 클라이언트는 추상 인터페이스를 통해 팩토리/제품 변형의 객체들과 소통하는 한 그 어떤 구상 팩토리/제품 변형과 작업할 수 있습니다.

의사코드

이 예시는 추상 팩토리 패턴이 크로스 플랫폼 사용자 인터페이스​(UI) 요소들을 생성하는 방법을 보여줍니다. 이 방법으로 요소들을 생성하면 클라이언트 코드는 구상 UI 클래스들과 결합하지 않으며, 모든 생성된 요소들은 선택된 운영 체제에 맞게 생성됩니다.

추상 팩토리 패턴 예시의 클래스 다이어그램

크로스 플랫폼 사용자 인터페이스 클래스 예시.

UI 요소들은 크로스 플랫폼 애플리케이션 내에서는 유사하게 동작할 것으로 예상되나, 다른 운영 체제 내에서는 약간씩 다르게 보일 것으로 예상됩니다. 또한 UI 요소들이 해당하는 운영 체제의 스타일과 일치하는지 확인하는 것도 당신의 책임입니다. 왜냐하면 당신은 당신의 프로그램이 윈도우에서 실행될 때 매킨토시 컨트롤을 렌더링하는 것을 원하지 않을 것이기 때문입니다.

추상 팩토리 인터페이스는 일련의 생성 메서드들을 선언하며, 이 메서드들은 클라이언트 코드가 다양한 유형들의 UI 요소들을 생성하는 데 사용될 수 있습니다. 구상 팩토리는 특정 운영 체제에 해당하고 해당 특정 운영체제에 일치하는 UI 요소들을 생성합니다.

작동 방식은 다음과 같습니다. 앱이 시작될 때 현재 소속된 운영 체제의 유형을 확인합니다. 그 후 앱은 이 정보를 사용하여 운영 체제와 일치하는 클래스에서 팩토리 객체를 생성합니다. 나머지 코드는 이 팩토리 객체를 사용하여 UI 요소들을 만듭니다. 이렇게 하면 잘못된 요소들이 생성되는 것을 방지할 수 있습니다.

위 방법을 사용하면 클라이언트 코드가 객체의 추상 인터페이스를 통해 작업하는 한 팩토리들과 UI 요소들의 구상 클래스들에 의존하지 않게 됩니다. 또 위 방법은 클라이언트 코드가 당신이 나중에 추가할 수 있는 다른 팩토리들과 UI 요소들을 지원할 수 있도록 합니다.

이것의 결과로 새로운 변형의 UI 요소를 앱에 추가할 때마다 클라이언트 코드를 수정할 필요가 없어집니다. 이 요소들을 생성하는 새로운 팩토리 클래스를 만든 후 앱의 초기화 코드를 그 팩토리 클래스를 선택하도록 약간 수정하기만 하면 됩니다.

// 추상 팩토리 인터페이스는 다른 추상 제품들을 반환하는 메서드들의 집합을
// 선언합니다. 이러한 제품들을 패밀리라고 하며 이들은 상위 수준의 주제 또는
// 개념으로 연결됩니다. 한 가족의 제품들은 일반적으로 서로 협력할 수 있습니다.
// 제품들의 패밀리​(제품군)​에는 여러 변형이 있을 수 있지만 한 변형의 제품들은 다른
// 변형의 제품들과 호환되지 않습니다.
interface GUIFactory is
    method createButton():Button
    method createCheckbox():Checkbox


// 구상 팩토리들은 단일 변형에 속하는 제품들의 패밀리​(제품군)​을 생성합니다. 이
// 팩토리는 결과 제품들의 호환을 보장합니다. 구상 팩토리 메서드의 시그니처들은 추상
// 제품을 반환하는 반면, 메서드 내부에서는 구상 제품이 인스턴스화됩니다.
class WinFactory implements GUIFactory is
    method createButton():Button is
        return new WinButton()
    method createCheckbox():Checkbox is
        return new WinCheckbox()

// 각 구상 팩토리에는 해당하는 제품 변형이 있습니다.
class MacFactory implements GUIFactory is
    method createButton():Button is
        return new MacButton()
    method createCheckbox():Checkbox is
        return new MacCheckbox()


// 제품 패밀리의 각 개별 제품에는 기초 인터페이스가 있어야 합니다. 이 제품의 모든
// 변형은 이 인터페이스를 구현해야 합니다.
interface Button is
    method paint()

// 구상 제품들은 해당하는 구상 팩토리에서 생성됩니다.
class WinButton implements Button is
    method paint() is
        // 버튼을 윈도우 스타일로 렌더링하세요.

class MacButton implements Button is
    method paint() is
        // 버튼을 맥 스타일로 렌더링하세요.

// 다음은 다른 제품의 기초 인터페이스입니다. 모든 제품은 상호 작용할 수 있지만 같은
// 구상 변형의 제품들 사이에서만 적절한 상호 작용이 가능합니다.
interface Checkbox is
    method paint()

class WinCheckbox implements Checkbox is
    method paint() is
        // 윈도우 스타일의 확인란을 렌더링하세요.

class MacCheckbox implements Checkbox is
    method paint() is
        // 맥 스타일의 확인란을 렌더링하세요.


// 클라이언트 코드는 GUIFactory, Button 및 Checkbox와 같은 추상 유형을
// 통해서만 팩토리들 및 제품들과 작동하며, 이는 클라이언트 코드를 손상하지 않고
// 클라이언트 코드에 모든 팩토리 또는 하위 클래스를 전달할 수 있게 해줍니다.
class Application is
    private field factory: GUIFactory
    private field button: Button
    constructor Application(factory: GUIFactory) is
        this.factory = factory
    method createUI() is
        this.button = factory.createButton()
    method paint() is
        button.paint()


// 앱은 현재 설정 또는 환경 설정에 따라 팩토리 유형을 선택한 후 팩토리를 런타임
// 때​(일반적으로는 초기화 단계에서) 생성합니다.
class ApplicationConfigurator is
    method main() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            factory = new WinFactory()
        else if (config.OS == "Mac") then
            factory = new MacFactory()
        else
            throw new Exception("Error! Unknown operating system.")

        Application app = new Application(factory)

적용

추상 팩토리는 당신의 코드가 관련된 제품군의 다양한 패밀리들과 작동해야 하지만 해당 제품들의 구상 클래스들에 의존하고 싶지 않을 때 사용하세요. 왜냐하면 이러한 클래스들은 당신에게 미리 알려지지 않았을 수 있으며, 그 때문에 향후 확장성​(extensibility)​을 허용하기를 원할 수 있기 때문입니다.

추상 팩토리는 제품 패밀리의 각 클래스에서부터 객체들을 생성할 수 있는 인터페이스를 제공합니다. 위 인터페이스를 통해 코드가 객체들을 생성하는 한 당신은 당신의 앱에서 이미 생성된 제품들과 일치하지 않는 잘못된 제품 변형을 생성하지 않을지 걱정할 필요가 없습니다.

코드에 클래스가 있고, 이 클래스의 팩토리 메서드들의 집합의 기본 책임이 뚜렷하지 않을 때 추상 팩토리 구현을 고려하세요.

잘 설계된 프로그램에서는 . 클래스가 여러 제품 유형을 상대할 경우, 클래스의 팩토리 메서드들을 독립실행형 팩토리 클래스 또는 완전한 추상 팩토리 구현으로 추출할 가치가 있을 수 있습니다.

구현방법

  1. 고유한 제품 유형들 대 변형 제품들을 나타내는 매트릭스를 매핑하세요.

  2. 모든 제품 변형들에 대한 추상 제품 인터페이스들을 선언하세요. 그 후 모든 구상 제품 클래스들이 위 인터페이스들을 구현하도록 하세요.

  3. 추상 팩토리 인터페이스를 모든 추상 제품들에 대한 생성 메서드들의 집합과 함께 선언하세요.

  4. 각 제품 변형에 대해 각각 하나의 구상 팩토리 클래스 집합을 구현하세요.

  5. 앱 어딘가에 팩토리 초기화 코드를 생성하세요. 초기화 코드는 앱 설정 또는 현재 환경에 따라 구상 팩토리 클래스 중 하나를 인스턴스화해야 합니다. 이 팩토리 객체를 제품을 생성하는 모든 클래스들에 전달하세요.

  6. 코드를 검토해서 제품 생성자에 대한 모든 직접 호출을 찾으세요. 이 호출들을 팩토리 객체에 대한 적절한 생성 메서드에 대한 호출들로 교체하세요.

장단점

  • 팩토리에서 생성되는 제품들의 상호 호환을 보장할 수 있습니다.
  • 구상 제품들과 클라이언트 코드 사이의 단단한 결합을 피할 수 있습니다.
  • . 제품 생성 코드를 한 곳으로 추출하여 코드를 더 쉽게 유지보수할 수 있습니다.
  • / . 기존 클라이언트 코드를 훼손하지 않고 제품의 새로운 변형들을 생성할 수 있습니다.
  • 패턴과 함께 새로운 인터페이스들과 클래스들이 많이 도입되기 때문에 코드가 필요 이상으로 복잡해질 수 있습니다.

다른 패턴과의 관계

  • 많은 디자인은 복잡성이 낮고 자식 클래스들을 통해 더 많은 커스터마이징이 가능한 팩토리 메서드로 시작해 더 유연하면서도 더 복잡한 추상 팩토리, 프로토타입 또는 빌더 패턴으로 발전해 나갑니다.

  • 빌더는 복잡한 객체들을 단계별로 생성하는 데 중점을 둡니다. 추상 팩토리는 관련된 객체들의 패밀리들을 생성하는 데 중점을 둡니다. 는 제품을 즉시 반환하지만 는 제품을 가져오기 전에 당신이 몇 가지 추가 생성 단계들을 실행할 수 있도록 합니다.

  • 추상 팩토리 클래스들은 팩토리 메서드들의 집합을 기반으로 하는 경우가 많습니다. 그러나 당신은 또한 프로토타입을 사용하여 추상 팩토리의 구상 클래스들의 생성 메서드들을 구현할 수도 있습니다.

  • 추상 팩토리는 하위시스템 객체들이 클라이언트 코드에서 생성되는 방식만 숨기고 싶을 때 퍼사드 대신 사용할 수 있습니다.

  • 당신은 추상 팩토리브리지와 함께 사용할 수 있습니다. 이 조합은 에 의해 정의된 어떤 추상화들이 특정 구현들과만 작동할 수 있을 때 유용합니다. 이런 경우에 는 이러한 관계들을 캡슐화하고 클라이언트 코드에서부터 복잡성을 숨길 수 있습니다.

  • 추상 팩토리들, 빌더들프로토타입들은 모두 싱글턴으로 구현할 수 있습니다.

코드 예시

C#으로 작성된 추상 팩토리 C++로 작성된 추상 팩토리 Go로 작성된 추상 팩토리 자바로 작성된 추상 팩토리 PHP로 작성된 추상 팩토리 파이썬으로 작성된 추상 팩토리 루비로 작성된 추상 팩토리 러스트로 작성된 추상 팩토리 스위프트로 작성된 추상 팩토리 타입스크립트로 작성된 추상 팩토리

추가 콘텐츠