Self-Dev/Design Patterns R&D

[14] 디자인 패턴 목록 - 생성 디자인 패턴 - 추상 팩토리 메서드(Abstract Factory)

Khadra 2024. 6. 19. 19:13

출처 : 디자인 패턴에 뛰어들기 - 알렉산더 슈베츠 도서




추상 팩토리

관련 객체들의 구상 클래스를 지정하지 않고도 관련 객체들의 모음을 생성할 수 있도록 하는 생성 패턴이다.

문제

가구 판매장을 위한 프로그램을 만든다고 가정해 보면 이 프로그램은 다음과 같은 클래스로 구성된다.

  • 1. 관련 제품들로 형성된 제품군
    • 예: Chair(의자), Sofa(소파), CoffeeTable(커피 테이블)
  • 2. 제품군의 여러 가지 변형
    • 예: Modern(현대식), Victorian(빅토리안), ArtDeco(아르데코 양식)

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

새로운 가구 객체를 생성할 때, 이 객체들이 기존 제품군 내의 다른 가구들과 일치하는 변형을 가지도록 해야 한다. 고객이 스타일이 일치하지 않는 가구 세트를 받으면 실망할 수 있기 때문이다.

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

또한, 가구 공급업체들은 카탈로그를 자주 변경하기 때문에, 새로운 제품이나 제품군을 추가할 때 기존 코드를 변경하지 않도록 하는 것이 중요하다.



해결책

추상 팩토리 패턴의 첫 단계는 각 제품군에 해당하는 개별적인 인터페이스를 명시적으로 선언하는 것이다. 모든 변형이 해당 인터페이스를 따르도록 규칙을 정한다.

  • 예를 들어, 모든 의자의 변형은 Chair(의자) 인터페이스를, 모든 커피 테이블 변형은 CoffeeTable(커피 테이블) 인터페이스를 구현한다.

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

다음 단계는 추상 팩토리 패턴을 선언하는 것이다.
추상 팩토리 패턴은 제품군 내의 모든 개별 제품들의 생성 메서드를 목록화하는 인터페이스이다.

  • 예를 들어, createChair(의자 생성), createSofa(소파 생성), createCoffeeTable(커피 테이블 생성) 등의 메서드를 포함한다.

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

제품 변형을 다루기 위해 각 제품군의 변형에 대해 추상 팩토리 인터페이스를 기반으로 별도의 팩토리 클래스를 생성한다.

  • 예를 들어, ModernFurnitureFactory(현대식 가구 팩토리)는 ModernChair(현대식 의자), ModernSofa(현대식 소파), ModernCoffeeTable(현대식 커피 테이블) 객체를 생성한다.

클라이언트 코드는 추상 인터페이스를 통해 팩토리와 제품 모두와 작동합니다. 이렇게 하면 클라이언트 코드를 변경하지 않고도 팩토리와 제품 변형을 자유롭게 교체할 수 있다.

클라이언트는 사용 중인 팩토리의 구상 클래스에 대해 알 필요가 없어야 한다.

클라이언트가 의자를 주문할 때, 클라이언트는 팩토리의 구상 클래스를 알 필요가 없다.
팩토리가 어떤 변형의 의자를 생성하는지도 신경 쓰지 않는다는 것이다.

클라이언트는 추상 Chair(의자) 인터페이스를 사용하여, 현대식이든 빅토리아식이든 모든 의자를 동일한 방식으로 주문한다. 생성된 의자의 변형은 항상 같은 팩토리 객체에서 생성된 소파나 커피 테이블의 변형과 일치합니다.

중요한 점

여기서 클라이언트가 추상 인터페이스에만 노출된다면 실제 팩토리 객체를 생성하는 것이 무엇이냐는 것이다.
일반적으로 프로그램은 초기화 단계에서 구상 팩토리 객체를 생성하며, 그 직전에 환경 또는 구성 설정에 따라 팩토리 유형을 선택해야 한다.



구조

  • 1. 추상 제품
    • 제품 패밀리를 구성하는 개별 연관 제품들의 인터페이스를 선언한다.
  • 2. 구상 제품
    • 추상 제품의 다양한 변형들을 구현한다.
    • 각 추상 제품(의자/소파)은 모든 변형(빅토리안/현대식)에 대해 구현된다.**
  • 3. 추상 팩토리 인터페이스
    • 각 추상 제품을 생성하는 여러 메서드의 집합을 선언한다.**
  • 4. 구상 팩토리
    • 추상 팩토리의 생성 메서드를 구현한다.
    • 각 구상 팩토리는 특정 제품 변형들만 생성한다.**
  • 5. 클라이언트와 결합되지 않은 구상 제품
    • 구상 팩토리는 구상 제품을 인스턴스화하나, 생성 메서드의 시그니처는 추상 제품을 반환해야 한다.
    • 즉, 클라이언트 코드는 특정 제품 변형과 결합되지 않고 추상 인터페이스를 통해 다양한 제품 변형과 작업할 수 있다.**

의사코드

이 예시는 추상 팩토리 패턴을 사용해 크로스 플랫폼 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)


적용

추상 팩토리는 코드가 다양한 제품군과 작동해야 하지만 구상 클래스들에 의존하지 않도록 할 때 사용한다.

  • 이렇게 하면 향후 확장성을 허용할 수 있다.

추상 팩토리는 제품군의 각 클래스에서 객체를 생성할 수 있는 인터페이스를 제공한다.

  • 이를 통해 잘못된 제품 변형을 생성하지 않도록 합니다.

팩토리 메서드의 책임이 불분명할 때 추상 팩토리를 고려해야 한다.

  • 각 클래스는 하나의 책임만 가져야 한다.
  • 여러 제품 유형을 다루는 클래스는 독립 팩토리 클래스 또는 추상 팩토리로 추출할 수 있다.

구현 방법

  • 1. 고유 제품 유형과 변형 제품들을 매핑한다.
  • 2. 모든 제품 변형에 대한 추상 제품 인터페이스를 선언하고, 구상 제품 클래스들이 이를 구현하도록 한다.
  • 3. 추상 팩토리 인터페이스를 모든 추상 제품 생성 메서드와 함께 선언힌다.
  • 4. 각 제품 변형에 대해 구상 팩토리 클래스를 구현한다.
  • 5. 앱 설정 또는 환경에 따라 구상 팩토리 클래스를 인스턴스화하는 초기화 코드를 생성한다.
    • 이 팩토리 객체를 모든 제품 생성 클래스에 전달한다.
  • 6. 제품 생성자에 대한 모든 직접 호출을 팩토리 객체의 생성 메서드 호출로 교체한다.

장단점

  • 상호 호환성 보장
    • 팩토리에서 생성되는 제품들이 서로 호환됨을 보장한다.
  • 결합도 감소
    • 구상 제품들과 클라이언트 코드 사이의 단단한 결합을 피할 수 있다.
  • 단일 책임 원칙
    • 제품 생성 코드를 한 곳으로 추출하여 유지보수가 용이하다.
  • 개방/폐쇄 원칙
    • 기존 클라이언트 코드를 수정하지 않고 새로운 제품 변형을 추가할 수 있다.
  • 복잡성 증가
    • 새로운 인터페이스와 클래스 도입으로 인해 코드가 복잡해질 수 있다.

다른 패턴과의 관계

  • 팩토리 메서드
    • 단순한 커스터마이징에서 시작해 추상 팩토리, 프로토타입, 빌더 패턴으로 발전할 수 있다.
  • 빌더
    • 복잡한 객체를 단계별로 생성하는 데 중점을 둔다.
    • 추상 팩토리는 관련 객체들의 패밀리를 즉시 반환한다.
  • 프로토타입
    • 추상 팩토리의 구상 클래스 생성 메서드들을 구현하는 데 사용할 수 있다.
  • 퍼사드
    • 하위시스템 객체들의 생성 방식을 숨기기 위해 사용할 수 있다.
  • 브리지
    • 특정 구현들과만 작동하는 추상화를 정의할 때 유용하다.
    • 추상 팩토리를 사용하여 이러한 관계를 캡슐화할 수 있다.
  • 싱글턴
    • 추상 팩토리, 빌더, 프로토타입 모두 싱글턴으로 구현할 수 있다.