Design Pattern - Abstract Factory

sam2·2022년 9월 20일
1

출처: https://refactoring.guru/design-patterns/abstract-factory
매끄럽게 읽기 위한 의역이 포함될 수 있습니다.
틀린 부분이 있다면 의견주세요.

Intent

Abstract Factory 란 구체적인 클래스에 의존(정)하지 않고 서로 연관되거나 의존적인 객체들의 조합을 만드는 인터페이스를 제공하는 패턴

Problem 🙁

당신이 한 가구점 시뮬레이터를 만들었다고 상상해보자. 그럼 당신의 코드는 아래 상황을 나타내는 클래스로 구성된다:
1. 연관있는 제품군: Chair + Sofa + CoffeTable.
2. 이 제품군의 다양성. 예를 들면, Chair + Sofa + CoffeTable 들은 Modern, Victorain, ArtDeco와 같은 형태가 가능할 것이다.

  • ArtDeco + Chair
  • ArtDeco + Sofa
  • ArtDeco + CoffeTable
  • Victorian + Chair
  • Victorian + Sofa
  • Victorian + CoffeTable
    ...

이제 동일한 제품군의 다른 가구들과 일치하도록 개별 가구를 작성하는 방법이 필요해졌다. 고객들은 일지하지 않는 가구를 받을 때 꽤 화가 날 테니까.
또한, 새 제품 또는 제품군을 프로그램에 추가할 때 코드를 수정하고 싶지 않을 것이다. 가구 공급업체는 그들의 카탈로그를 매우 자주 업데이트 할 것이고, 그 때마다 수정하고 싶지 않을 것이다.

Solution 😎

Abstract Factory 패턴을 정의할 때 제일 먼저 해야 할 것은 제품군의 개별 제품 (예: 의자, 소파 또는 커피 테이블)에 대한 인터페이스를 명시적으로 선언하는 것이다. 그러면 당신은 모든 제품의 다양성(위에서 말한 Modern, Victorian같은 가구테마)에 따른 제품군 역시 이 인터페이스를 따르도록 할 수 있다. 예를 들면, 모든 의자제품군은 Chair 라는 인터페이스를 구현하고, 커피테이블 역시 CoffeTable 인터페이스를 구현할 수 있다.

다음에 할 행동은 Abstract Factory를 정의하는 것이다─제품 군에 속하는 생산품을 만들기 위한 생성 메서드(creation method)의 리스트들(예: createChair, createSofa, createCoffeTable)로 된 인터페이스를 의미한다. 이 메서드들은 우리가 이전에 추출한 인터페이스(Chair, Sofa, CoffeTable 등)로 나타나는 추상적인(abstract) 제품타입을 반환해야 한다.

그럼, 다른 제품은 어떨까? 각기 다른 제품군들이 있다면, 우린 Abstract Factory 인터페이스를 기초로 한 별도의 팩토리 클래스를 만들면 된다. 이 팩토리 클래스는 특정 제품군의 제품을 반환한다. 예를 들면, ModernFurnitureFactory 는 오직 ModernChair, ModernSofa, ModernCoffeeTable 객체를 만든다는 것이다.

클라이언트 코드는 이 두 공장(factory class들)과 추상적인 인터페이스를 통해 만들어진 생산품과 같이 동작할 것이다. 이것은 실제 클라이언트 코드를 손상시키지 않으면서, 클라이언트코드가 받을 제품의 다양성 뿐만 아니라 클라이언트 코드로 전달한 팩토리의 타입을 변경할 수 있게 해준다(원문: This lets you change the type of a factory that you pass to the client code, as well as the product variant that the client code receives, without breaking the actual client code.).

클라이언트는 팩토리에서 의자를 원한다고 얘기한다고 생각해보자. 클라이언트는 그 공장의 클래스에서 어떤 종류의 의자를 만들 수 있는지 알 필요도 없고 신경쓰지 않는다. 그게 모던한 모델이든, 빅토리안 스타일이든 추상화된 Chair 인터페이스를 사용해서 만든 모든 의자가 동일한 방식으로 다룰 수 있어야한다. 이 방식을 고려했을 때, 클라이언트가 의자에 대해 유일하게 알고 있는 방법은 의자는 어떤 식으로든 sitOn 메서드를 구현한다는 것이다. 또한, 어떤 변형된 의자가 나오더라도, 동일한 공장에서 생산된 소파 또는 커피테이블의 타입과 항상 일치해야할 것이다(원문: Also, whichever variant of the chair is returned, it’ll always match the type of sofa or coffee table produced by the same factory object.).

명확하게 정의해야 할 것이 하나 남았다: 만약 클라이언트에는 오로지 추상적인 인터페이스만 노출되어 있다면, 그럼 실제로 공장객체는 누가 만들까? 보통은, 애플리케이션이 초기화 단계를 진행할 때 구체적인 공장객체를 만든다. 그러니 그 전에, 애플리케이션은 반드시 구성 또는 환경 설정에 맞는 공장유형을 선택해야 한다.

구조


1. 제품 추상화(Abstract Product): 제품군을 구성하는 별개의 관련 제품 세트에 대한 인터페이스를 선언한다.
2. 구체적인 제품(Concreate Products): 구체적인 제품은 변형별(라인별)로 그룹화 된 추상 제품에 대한 다양한 구현이다. 각 추상적인 제품 (의자 / 소파)은 주어진 모든 라인 (빅토리아 / 모던)으로 구현할 수 있어야 한다.
3. Abstract Factory의 인터페이스는 각 추상적인 제품을 만드는데 필요한 메서드들의 집합으로 선언한다.
4. 구체적인 팩토리들은 Abstract Factory의 생성 메서드들로 구현한다. 각 구체적인 공장은 제품의 특정한 제품군과 일치하며 오로지 그 제품군과 맞게 만든다.
5. 구체적인 팩토리는 구체적인 제품을 인스턴스화하지만 생성 방법은 해당 추상 제품을 반환해야한다. 이렇게하면 공장을 사용하는 클라이언트 코드가 공장에서 가져온 제품의 특정 변형에 결합되지 않는다. 클라이언트는 추상 인터페이스를 통해 객체와 통신하는 한 모든 구체적인 공장 / 제품 변형과 함께 작업 할 수 있습니다.

의사코드

// 추상적인 팩토리 인터페이스는 다른 추상적인 제품을 반환하는 메서드 집합을 선언한다.
// 이런 제품들은 제품군들이라 불리며 이 것들은 보다 높은 수준의 테마 및 컨셉과 관련이 있다.
// 동일한 제품군의 제품들은 대개 동일 라인의 다른 제품과 협력(collaborate)이 가능하다.
// 제품군 안에는 몇몇의 변형제품들(소파, 커피테이블 같은)이 있을 수 있지만 
// 그 제품군에서 나온 하나의 제품은 변형된 다른 제품과 호환되지 않는다.
interface GUIFactory {
    createButton():Button
    createCheckbox():Checkbox
}
// 구체적인 공장들은 하나의 변형을 만들 수 있는 제품군을 생성한다.
// 그리고 그 공장은 만들어진 제품이 호환가능함을 보장한다.
// Signatures of the concrete factory's methods return an abstract product, while inside the method a concrete product is instantiated.
class WinFactory implements GUIFactory {
    public createButton():Button {
        return new WinButton()
    }
    public createCheckbox():Checkbox {
        return new WinCheckbox()
    }
}
//각 구체적인 팩토리는 제품의 특성에 맞게 한다.
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)
profile
고양이 두마리를 모시고 있는 프론트엔드 코더(?)

0개의 댓글