Abstract Factory

추상 팩토리, Kit 이라고도 불린다.

사용 의도

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

사용 동기

  • 응용 프로그램이 서로 다른 모습인 것과 상관 없이 이식성을 갖기 위해서는 응용 프로그램이 각 사용자 인터페이스에서 제공하는 위젯을 직접 사용하지 못하게 해야함

활용 가능한 상황

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

구체적 예시

문제 상황

  • 가구 상점 시뮬레이터를 만들고 있다고 가정했을 때, 코드는 아래를 나타내는 클래스를 구성하고 있어야 한다.

    • 연관된 상품군 (소파, 의자, 커피 테이블)
    • 몇 가지의 구체적인 상품 회사 (IKEA, E-mart, etc)
  • 개별 상품을 만들 수 있고, 다른 오브젝트와 매치시켜 같은 상품 회사의 연관된 상품을 만들어낼 수 있어야 한다.

    ex. IKEA 의자를 산 손님은 추가 주문 시 IKEA 소파를 사고 싶어 한다.

  • 또한, 새로운 상품이나, 회사를 추가하더라도 이미 존재하는 코드들을 수정하고 싶지 않다. 가구 상인은 그들의 카탈로그를 자주 업데이트하기 때문에, 그때마다 코드를 수정하고 싶지는 않다.

해결 방법

명시적으로 각자 상품군의 인터페이스를 선언한다. 그러면 다양한 종류의 상품을 만들어낼 수 있다.

sofa.PNG

그 다음은 생성 메소드들(createChair, createSofa, createCoffeeTable)을 가진 상품 회사 Abstract Factory를 선언하는 것이다. 이 함수들은 반드시 이전에 우리가 추출했던 인터페이스에 의해 표현된 추상 상품 타입들을 반환해야 한다. (createChar(): Chair, createSofa(): Sofa)

furniturefactory.png

이제 각 상품 회사의 상품들의 factory class를 AbstractFactory 인터페이스를 기반으로 만들어낼 수 있다. 하나의 팩토리는 특정 종류의 상품을 리턴하는 클래스이다. 예를 들어서 IKEAFactory는 IKeaChair를 만들 수 있다.

참여자

  • AbstractFactory: 개념적 제품에 대한 객체를 생성하는 연산, 인터페이스를 정의함
  • ConcreteFactory: 구체적인 제품에 대한 객체를 생성하는 연산을 구현함
  • AbstractProduct: 개념적 제품 객체에 대한 인터페이스를 정의함
  • ConcreteProduct: 구체적으로 팩토리가 생성할 객체를 정의한다. AbstractProduct가 정의하는 인터페이스를 구현한다.
  • Client: AbstractFactoryAbstractProduct 클래스에 선언된 인터페이스를 사용한다.

협력 방법

  • 일반적으로 ConcreteFactory 클래스의 인스턴스 한 개가 런타임에 만들어진다. 이 구체 팩토리는 어떤 특정 구현을 갖는 제품 객체를 생성한다. 서로 다른 제품 객체를 생성하려면 사용자는 서로 다른 구체 팩토리를 사용해야 한다.
  • AbstractFactory는 필요한 제품 객체를 생성하는 책임을 ConcreteFactory서브 클래스에 위임한다.

협력 결과

:)

  • 구체적인 클래스를 분리한다.
  • 제품군을 쉽게 대체할 수 있게 한다.
  • 제품 사이의 일관성을 증진시킨다.

:(

  • 새로운 종류의 제품을 제공하기 어렵다.
    새로운 종류의 제품을 만들기 위해 기존 추상 팩토리를 확장하기 쉽지 않다. 모든 서브 클래스의 변경을 초래하기 때문에.

코드 (Typescript)

Code

interface AbstractFactory {
  createChair(): AbstractChair;
  createSofa(): AbstractSofa;
}

interface AbstractSofa {
  sitOn(): string;
  rest(): string;
  legOn(collaborator: AbstractChair): string;
}

interface AbstractChair {
  sitOn(): string;
  putLeg(): string;
}

class IkeaFactory implements AbstractFactory {
  public createChair(): AbstractChair {
    return new ConcreteIkeaChair();
  }

  public createSofa(): AbstractSofa {
    return new ConcreteIkeaSofa();
  }
}

class EMartFactory implements AbstractFactory {
  public createChair(): AbstractChair {
    return new ConcreteEmartChair();
  }

  public createSofa(): AbstractSofa {
    return new ConcreteEmartSofa();
  }
}

class ConcreteIkeaChair implements AbstractChair {
  sitOn(): string {
    return `sit on Ikea chair`;
  }
  putLeg(): string {
    return `put your leg on Ikea chair`;
  }
}

class ConcreteIkeaSofa implements AbstractSofa {
  sitOn(): string {
    return "sit on Ikea sofa";
  }
  rest(): string {
    return "rest on Ikea sofa";
  }
  legOn(collaborator: AbstractChair): string {
    return `with Ikea Sofa, leg on chair: ${collaborator.putLeg()}`;
  }
}

class ConcreteEmartChair implements AbstractChair {
  sitOn(): string {
    return `sit on Emart chair`;
  }
  putLeg(): string {
    return `put your leg on Emart chair`;
  }
}

class ConcreteEmartSofa implements AbstractSofa {
  sitOn(): string {
    return "sit on Emart sofa";
  }
  rest(): string {
    return "rest on Emart sofa";
  }
  legOn(collaborator: AbstractChair): string {
    return `with Emart Sofa, leg on chair: ${collaborator.putLeg()}`;
  }
}

const clientCode = (factory: AbstractFactory) => {
  const chair = factory.createChair();
  const sofa = factory.createSofa();

  console.log(sofa.rest());
  console.log(sofa.legOn(chair));
};

clientCode(new IkeaFactory());

clientCode(new EMartFactory());

Output

rest on Ikea sofa
with Ikea Sofa, leg on chair: put your leg on Ikea chair

rest on Emart sofa
with Emart Sofa, leg on chair: put your leg on Emart chair

Ref