Abstract Factory

chrishan·2021년 3월 21일
0

Design pattern

목록 보기
2/12

Intent

Abstract Factory는 구체적인 클래스를 지정하지 않고 관련성을 갖는 객체들의 집합을 생성하거나 서로 독립적인 객체들의 집합을 생성할 수 있는 인터페이스를 제공하는 패턴이다.

Problem

가구점 시뮬레이터를 만들고 있다고 생각해보자. 코드는 다음을 나타내는 클래스로 구성된다:

  1. 관련 제품: Chair + Sofa + CoffeeTable.
  2. 관련 제품의 변수. 예를 들어 Chair + Sofa + CoffeeTable 제품은Modern, Victorian, ArtDeco 시리즈로 제공될 수 있다.

동일 시리즈의 개별 가구를 생성하는 방법이 필요하다. 고객은 서로 다른 시리즈의 가구들을 받으면 화가 날 것이다.

또한 새 제품이나 제품군을 추가 할 때 기존 코드를 변경하고 싶은 개발자는 없을 것이다. 가구 공급 업체는 카탈로그를 자주 업데이트하므로 매번 핵심 코드를 변경하는 것은 위험하다.

Solution

Abstract Factory 패턴이 제안하는 첫 번째 사항은 제품군의 개별 제품(e.g., chair, sofa or coffee table)에 대한 interface를 명시적으로 선언하는 것이다. 그런 다음 제품의 모든 변형이 해당 interface를 따르도록 할 수 있다. 예를 들어 모든 의자 변형은 Chair interface; 모든 커피 테이블 변형은 CoffeeTable interface 등을 구현할 수 있다.

다음 단계는 제품군의 일부인 모든 제품에 대한 생성 method 목록이 있는 interface인 Abstract Factory를 선언하는 것이다 (예: createChair, createSofa and createCoffeeTable). 이러한 method는 이전에 추출한 interface로 표현되는 추상 제품 유형인 Chair, Sofa, CoffeeTable등을 반환해야 한다.

이제 제품 변형은 어떠한가? 제품군의 각 변형에 대해AbstractFactory interface를 기반으로 별도의 Factory class를 만든다. Factory는 특정 시리즈의 제품을 반환하는 클래스이다. 예를 들어 ModernFurnitureFactoryModernChair, ModernSofa 그리고 ModernCoffeeTable 객체만 만들 수 있다.

클라이언트 코드는 각각의 Abstract interface를 통해 공장과 제품 모두에서 작동해야 한다. 이를 통해 실제 클라이언트 코드를 손상시키지 않고 클라이언트 코드에 전달되는 Factory 유형과 클라이언트 코드가 수신하는 제품 변형을 변경 할 수 있다.

Client가 Factory에서 의자를 생산하기를 원한다고 가정 해보자. Client는 Factory class를 알 필요가 없으며 어떤 종류의 의자를 가져가는 것도 중요하지 않다. 모던 시리즈든 빅토리아 시리즈든 Client는 추상적인Chair interface를 사용하여 모든 의자를 동일한 방식으로 취급해야 한다. 이 접근 방식을 사용하면 Client가 의자에 대해 아는 유일한 방법은sitOn method를 어떤 방식으로든 구현하는 것이다. 또한 어떤 변형 의자가 반환 되든 항상 동일한 Factory object에서 생산 된 Sofa 또는 CoffeeTable 유형과 일치하다.

명확히 해야 할 것이 한가지 더 남았다. Client가 Abstract interface에만 노출되는 경우 실제 Factory object를 만드는 것은 무엇인가? 일반적으로 Application은 초기화 단계에서 구체적인 Factory object를 생성한다. 그 전에 Application은 Configuration 또는 Environment settings에 따라 Factory 유형을 선택해야 한다.

Structure


1. Abstract Products는 제품군을 구성하는 별개의 관련 제품 세트에 대한 인터페이스를 선언합니다.

2. Concrete Products는 변형별로 그룹화된 추상 제품의 다양한 구현입니다. 각 Abstract Product(의자/소파)은 주어진 모든 변형(Victorian/Modern)으로 구현되어야 합니다.

3. Abstract Factory 인터페이스는 각각의 추상 제품을 생성하기 위한 메소드 세트를 선언합니다.

4. Concrete Factories는 추상 팩토리의 생성 방법을 구현합니다. 각 concrete factory는 제품의 특정 변형에 해당하며 해당 제품 변형만 생성합니다.

5. Concrete Factory는 concrete product를 인스턴스화하지만 생성 방법의 signature는 해당 abstract product를 반환해야 합니다. 이렇게 하면 팩토리를 사용하는 클라이언트 코드가 팩토리에서 가져온 제품의 특정 변형에 연결되지 않습니다. Client는 추상 인터페이스를 통해 개체와 통신하는 한 구체적인 공장/제품 변형으로 작업할 수 있습니다.

Pseudocode

이 예에서는 생성 된 모든 요소를 선택한 운영 체제와 일관성있게 유지하면서 클라이언트 코드를 구체적인 UI 클래스에 연결하지 않고 크로스 플랫폼 UI 요소를 만드는 데 Abstract Factory 패턴을 사용하는 방법을 보여준다.


The cross-platform UI classes example.
Cross platform application의 동일한 UI요소는 유사하게 작동 할 것으로 예상되지만 운영 체제에 따라 약간 다르게 보인다. 또한 UI 요소가 현재 운영 체제의 스타일과 일치하는지 확인하는 것이 귀하의 임무이다. 프로그램이 Windows에서 실행될 때 macOS 컨트롤을 렌더링 하는 것을 원하지 않을 것이다.

Abstract Factory 인터페이스는 클라이언트 코드가 다양한 유형의 UI 요소를 생성하는 데 사용할 수 있는 생성 메서드 집합을 선언한다. 구체적인 공장은 특정 운영 체제에 해당하고 특정 OS와 일치하는 UI 요소를 만든다.

다음과 같이 동작: Application이 시작될 때 현재 운영 체제의 유형을 확인한다. App은 정보를 사용하여 운영 체제와 일치하는 클래스에서 팩토리 개체를 만든다. 나머지 코드는 이 팩토리를 사용하여 UI 요소를 만든다. 이렇게 하면 잘 못된 요소가 생성되는 것을 방지한다.

이 접근 방식을 사용하면 client 코드가 Abstract interface를 통해 이러한 개체와 함께 동작하는 한 Factory 및 UI 요소의 구체적인 클래스에 의존하지 않는다. 이를 통해 client 코드는 나중에 추가 할 수 있는 다른 Factory 또는 UI 요소를 지원할 수도 있다.

따라서 App에 새로운 UI 요소 변형을 추가 할 때마다 client 코드를 수정할 필요가 없다. 이러한 요소를 생성하는 새 Factory class를 만들고 앱의 초기화 코드를 약간 수정하여 적절한 경우 해당 Class를 선택하면 된다.

// The abstract factory interface declares a set of methods that
// return different abstract products. These products are called
// a family and are related by a high-level theme or concept.
// Products of one family are usually able to collaborate among
// themselves. A family of products may have several variants,
// but the products of one variant are incompatible with the
// products of another variant.
interface GUIFactory is
    method createButton():Button
    method createCheckbox():Checkbox


// Concrete factories produce a family of products that belong
// to a single variant. The factory guarantees that the
// resulting products are compatible. Signatures of the concrete
// factory's methods return an abstract product, while inside
// the method a concrete product is instantiated.
class WinFactory implements GUIFactory is
    method createButton():Button is
        return new WinButton()
    method createCheckbox():Checkbox is
        return new WinCheckbox()

// Each concrete factory has a corresponding product variant.
class MacFactory implements GUIFactory is
    method createButton():Button is
        return new MacButton()
    method createCheckbox():Checkbox is
        return new MacCheckbox()


// Each distinct product of a product family should have a base
// interface. All variants of the product must implement this
// interface.
interface Button is
    method paint()

// Concrete products are created by corresponding concrete
// factories.
class WinButton implements Button is
    method paint() is
        // Render a button in Windows style.

class MacButton implements Button is
    method paint() is
        // Render a button in macOS style.

// Here's the base interface of another product. All products
// can interact with each other, but proper interaction is
// possible only between products of the same concrete variant.
interface Checkbox is
    method paint()

class WinCheckbox implements Checkbox is
    method paint() is
        // Render a checkbox in Windows style.

class MacCheckbox implements Checkbox is
    method paint() is
        // Render a checkbox in macOS style.


// The client code works with factories and products only
// through abstract types: GUIFactory, Button and Checkbox. This
// lets you pass any factory or product subclass to the client
// code without breaking it.
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()


// The application picks the factory type depending on the
// current configuration or environment settings and creates it
// at runtime (usually at the initialization stage).
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)

Applicability

코드가 다양한 관련 제품군과 함께 동작해야만 하지만 제품의 구체적인 Class에 의존하지 않으려는 경우 Abstract Factory를 사용하라.

Abstract Factory는 제품군의 각 Class에서 객체를 생성하기 위한 Interface를 제공한다. 코드가 이 Interface를 통해 객체를 생성하는 한 App에서 이미 생성 한 제품과 일치하지 않는 제품의 잘못 된 변형을 생성하는 것에 대해 걱정할 필요가 없다.

  • 기본 책임을 모호하게 하는 Factory Methods 집합이 있는 Class가 있을 경우 Abstract Factory 구현을 고려하라.

  • 잘 설계된 프로그램에서 각 Class는 한 가지만 담당한다. Class가 여러 제품 유형을 다룰 때, Factory Method를 독립형 Factory Class 또는 완전한 Abstract Factory 구현으로 추출 할 가치가 있다.

How to Implement

  1. 고유한 제품 유형과 이러한 제품의 변형 매트릭스를 매핑 한다.

  2. 모든 제품 유형에 대한 Abstract Interface를 선언한다. 그런 다음 모든 구체적인 제품 Class가 이러한 Interface를 구현하도록 한다.

  3. 모든 추상 제품에 대한 일련의 생성 방법을 사용하여 Abstract Factory Interface를 선언한다.

  4. 각 제품 변형에 대해 하나씩 구체적인 Factory Class 집합을 구현한다.

  5. App 어딘가에 Factory initialization code를 만든다. Application 구성이나 현재 환경에 따라 Concrete factory class 중 하나를 인스턴스화 해야 한다. 이 Factory object를 제품을 구성하는 모든 Class에 전달한다.

  6. Code를 스캔하여 제품 생성자에 대한 모든 직접 호출을 찾는다. Factory object에 대한 적절한 Creation method에 대한 호출을 대체한다.

Pros and Cons

O. Factory에서 얻어지는 제품이 서로 호환되는지 확인 할 수 있다.
O. 구체적인 제품과 Client code간의 긴밀한 결합을 피한다.
O. Single Responsibility Principle. 제품 생성 코드를 한 곳으로 추출하여 코드를 더 쉽게 지원할 수 있다.
O. Open/Closed Principle. 기존 Client code를 손상시키지 않고 새로운 제품 변형을 도입 할 수 있다.
X. 패턴과 함께 새로운 Interface와 Class가 도입 되었기 때문에 코드는 생각보다 복잡해 질 수 있다.

Relations with Other Patterns

  • 만은 디자인은 Factory Method (비교적 간단하다. 그리고 Sub Class를 통해 더 다양한 사용자 정의가 가능하다)를 사용하여 시작하고 Abstract Factory, Prototype, 또는 Builder (더 유연하지만 더 복잡함)로 발전한다.

  • Builder는 복잡한 개체를 단계별로 구성하는 데 중점을 둔다. Abstract Factory는 관련 개체의 패밀리 생성을 전문으로 한다. Abstract Factory는 제품을 즉시 반환하지만 Builder를 사용하면 제품을 가져 오기 전에 몇 가지 추가 구성 단계를 실행 할 수 있다.

  • Abstract Factory class는 종종 Factory Methods 집합을 기반으로 하지만, Prototype을 사용하여 이러한 Class에 대한 Method를 구성 할 수도 있다.

  • Client code에서 subsystem 개체가 생성되는 방식만 숨기려는 경우 Abstract FactoryFacade 대신 사용할 수 있다.

  • Bridge와 함께 Abstract Factory를 사용할 수 있다. 이는 Bridge에서 정의한 일부 추상화가 특정 구현에서만 동작 할 수 있는 경우에 유용하다. 이 경우 Abstract Factory는 이러한 관계를 캡슐화하고 Client code에서 복잡성을 숨길 수 있다.

  • Abstract Factories, Builders 그리고 Prototypes은 모두 Singletons으로 구현할 수 있다.


원문: https://refactoring.guru/design-patterns/abstract-factory

0개의 댓글