[Design Pattern] 추상 팩토리 패턴 (Abstract Factory Pattern)

Martin Kim·2022년 3월 9일
0

디자인패턴

목록 보기
3/6

의도

  • 상세화된(구체적인) 서브클래스를 정의하지 않고도 서로 관련성 있거나 독립적인 여러 객체의 군(패밀리)를 생성하기 위한 인터페이스를 제공하기 위함

참여자

  • AbstractFactory: 개념적 제품에 대한 객체를 생성하는 연산으로 인터페이스를 정의

  • ConcreteFactory: 구체적인 제품에 대한 객체를 생성하는 연산을 구현(AbstractFactory의 인터페이스 구현)

  • AbstractProduct: 개념적 제품 객체에 대한 인터페이스를 정의

  • ConcreteProduct:구체적으로 팩토리가 생성할 객체를 정의하고 AbstractProduct가 정의하는 인터페이스를 구현

  • Client: AbstractFactory와 AbstractProduct 클래스에 선언된 인터페이스를 사용한다.(사용하는 쪽에서의 코드라고 생각)

예시

가구 매장 시뮬레이터를 만들고 있다고 가정하자,

  1. 관련된 제품들의 패밀리를 다음과 같이 말할 수 있다: Chair + Sofa + CoffeeTable

  2. 예를 들어, Chair + Sofa + CoffeeTable의 제품들은 Modern, Victorian, Art Deco로 묶일 수 있다.

이제 우린 같은 패밀리의 다른 객체들과 어울리는 개인의 가구 객체를 생성해야 한다. 손님들은 어울리지 않은 가구를 받으면 매우 화를 낼 것이다.

예를 들어 Modern 스타일의 Sofa는 Victoria 스타일의 Chair과 어울리지 않을 것이다.

또한 프로그램에 새 제품이나 제품군을 추가할 때 기존 코드를 변경하고 싶지 않을 것이다. 가구 매장은 카달로그를 매우 자주 업데이트하므로 업데이트 할 때 마다 핵심 코드를 변경하고 싶지 않을 것이다.

해결방법

  1. 먼저 Chair, Sofa, CoffeTable같이 구별된 제품 패밀리의 개별 제품에 대한 인터페이스를 명시적으로 선언한다. 그리고 나면 제품의 모든 변형이 이러한 인터페이스를 따르게 할 수 있다.

  2. 제품군의 일부인 모든 제품에 대한 생성 방법이 있는 인터페이스인 Abstract Factory를 선언한다. 추상 팩토리의 메서드는 이전에 추출한 인터페이스가 나타내는 Abstract Product를 반환해야 한다.

이제 이렇게 되면 Abstract Factory 인터페이스를 기반으로(예시에는 Furniture Factory)를 기반으로 패밀리끼리의 생성을 담당하는 Factory클래스를 통해 특정 패밀리의 제품을 반환할 수 있다. 예를 들어 Modern 패밀리의 제품을 생성하고 싶다면 ModernFurnitureFactory의 메서드를 통해 생성할 수 있다.

예시를 보고 느낀점은 클래스를 본연의 구조적, 개념적인 기준이 아닌, 어떤 특별한 기준으로 묶고, 그 기준에 따라 객체를 생성하고자 할 때 사용하는 패턴이라고 생각.

손님이 공장에서 의자를 생산하기를 원한다고하면, 손님은 공장의 클래스를 알 필요도 없고 어떤 종류의 의자를 가져오는지도 중요하지 않다. Modern 스타일이던, Victorian 스타일의 의자이든 손님은 어쨌든 추상 Chair 인터페이스를 사용하여 모든 의자를 동일하게 취급한다. 이런 방식으로 손님이 의자에 대해 알아야 할 단 한가지는 의자가 sitOn 메서드를 같은 방식으로 구현했는지 이다. 또한 어떤 의자가 반환되던간 동일한 공장에서 생상된 Sofa, CoffeeTable의 유형과 항상 일치한다.(같은 공장에서 생성하므로)

구현방법

  1. 이러한 제품의 변형에 대한 고유한 제품 유형의 매트릭스를 매핑시킨다.
  2. 모든 제품 유형에 대한 추상 제품 인터페이스를 선언한다. 그리고 모든 구체 제품 클래스가 인터페이스를 구현하도록 한다.
  3. 모든 추상 제품에 대한 생성 메서드 집합으로 추상 팩토리 인터페이스를 선언한다.
  4. 각 제품 변형에 대해 하나씩 구체 팩토리 클래스 세트를 구현한다(추상 팩토리 인터페이스를 구현한).
  5. 어딘가에 공장 초기화 코드를 만들어서 구체적인 팩토리 클래스를 인스턴스화 한다.
  6. 제품 생성자에 대한 모든 직접적인 호출들을 팩토리 객체의 적절한 생성메서드 호출로 교체한다.

활용성

  • 객체가 생성되거나 구성, 표현되는 방식과는 상관없이 시스템을 독립적으로 만들고자 할 때
  • 여러 제품군 중 하나를 선택해서 시스템을 설정해야 하고 한번 구성한 제품을 다른 것으로 대체할 수 있을 때
  • 관련된 제품 객체들이 함께 사용되도록 설계되었고, 이 부분에 대한 제약이 외부에도 지켜지도록 하고 싶을 때
  • 제품에 대한 클래스 라이브러리를 제공하고, 그들의 구현이 아닌 인터페이스를 노출시키고자 할 때

장점

  • 생성한 객체가 서로 호환되는지(같은 패밀리에 속하는지) 확인할 수 있다.
  • 구체적인 객체와 클라이언트 코드 간의 긴밀한 결합을 느슨하게 할 수 있다.
  • 단일 책임 원칙(Single Responsibility Principle. 모든 클래스는 하나의 책임만). 객체 생성 코드를 한 곳으로 추출하여 원칙을 지킬 수 있다.
  • 개방/폐쇄 원칙(Open-Closed Principle. 확장은 개방적으로, 수정은 폐쇄적으로). 기존 클라이언트의 코드는 손상시키지 않고 제품의 새로운 변형을 도입할 수 있다.

단점

  • 많은 새로운 인터페이스와 클래스가 패턴과 함께 도입되므로 코드가 복잡해 질 수 있다.

In Swift

import XCTest

// 추상 팩토리 인터페이스 선언
protocol AbstractFactory {

    func createProductA() -> AbstractProductA
    func createProductB() -> AbstractProductB
}

// 추상 팩토리를 인터페이스를 구현하는 구체 팩토리 클래스1
class ConcreteFactory1: AbstractFactory {

    func createProductA() -> AbstractProductA {
        return ConcreteProductA1()
    }

    func createProductB() -> AbstractProductB {
        return ConcreteProductB1()
    }
}

// 추상 팩토리를 인터페이스를 구현하는 구체 팩토리 클래스2
class ConcreteFactory2: AbstractFactory {

    func createProductA() -> AbstractProductA {
        return ConcreteProductA2()
    }

    func createProductB() -> AbstractProductB {
        return ConcreteProductB2()
    }
}

// 제품이 구현해야 하는 추상 제품 인터페이스1
protocol AbstractProductA {

    func usefulFunctionA() -> String
}

// 추상 제품 인터페이스1을 구현한 구체 제품 클래스1
class ConcreteProductA1: AbstractProductA {

    func usefulFunctionA() -> String {
        return "The result of the product A1."
    }
}

// 추상 제품 인터페이스1을 구현한 구체 제품 클래스2
class ConcreteProductA2: AbstractProductA {

    func usefulFunctionA() -> String {
        return "The result of the product A2."
    }
}

// 제품이 구현해야 하는 추상 제품 인터페이스2
protocol AbstractProductB {

    /// Product B is able to do its own thing...
    func usefulFunctionB() -> String

    /// ...but it also can collaborate with the ProductA.
    ///
    /// The Abstract Factory makes sure that all products it creates are of the
    /// same variant and thus, compatible.
    func anotherUsefulFunctionB(collaborator: AbstractProductA) -> String
}

// 추상 제품 인터페이스2을 구현한 구체 제품 클래스1
class ConcreteProductB1: AbstractProductB {

    func usefulFunctionB() -> String {
        return "The result of the product B1."
    }

    /// This variant, Product B1, is only able to work correctly with the
    /// variant, Product A1. Nevertheless, it accepts any instance of
    /// AbstractProductA as an argument.
    func anotherUsefulFunctionB(collaborator: AbstractProductA) -> String {
        let result = collaborator.usefulFunctionA()
        return "The result of the B1 collaborating with the (\(result))"
    }
}

// 추상 제품 인터페이스2을 구현한 구체 제품 클래스2
class ConcreteProductB2: AbstractProductB {

    func usefulFunctionB() -> String {
        return "The result of the product B2."
    }

    /// This variant, Product B2, is only able to work correctly with the
    /// variant, Product A2. Nevertheless, it accepts any instance of
    /// AbstractProductA as an argument.
    func anotherUsefulFunctionB(collaborator: AbstractProductA) -> String {
        let result = collaborator.usefulFunctionA()
        return "The result of the B2 collaborating with the (\(result))"
    }
}

// 제품을 사용하는 클라이언트 측의 코드, factory 의존성을 주입받는다.
class Client {
    // ...
    static func someClientCode(factory: AbstractFactory) {
        let productA = factory.createProductA()
        let productB = factory.createProductB()

        print(productB.usefulFunctionB())
        print(productB.anotherUsefulFunctionB(collaborator: productA))
    }
    // ...
}

/// Let's see how it all works together.
class AbstractFactoryConceptual: XCTestCase {

    func testAbstractFactoryConceptual() {

        /// The client code can work with any concrete factory class.

        print("Client: Testing client code with the first factory type:")
        Client.someClientCode(factory: ConcreteFactory1())

        print("Client: Testing the same client code with the second factory type:")
        Client.someClientCode(factory: ConcreteFactory2())
    }
}

출처: GoF의 디자인패턴, https://refactoring.guru/design-patterns/swift

profile
학생입니다

0개의 댓글