Abstarct Factory 패턴
출처: https://refactoring.guru/ko
리팩터링과 디자인 패턴
Hello, world! Refactoring.Guru는 리팩토링, 디자인 패턴, SOLID 원칙 및 기타 스마트 프로그래밍 주제에 대해 알아야 할 모든 것을 쉽게 찾을 수 있는 자원입니다. 이 사이트에서는 이러한 모든 주제가 어
refactoring.guru
추상 팩토리는 관련 객체들의 구상 클래스들을 지정하지 않고도 관련 객체들의 모음을 생성할 수 있도록 하는 생성패턴입니다.

문제
예를 들어 당신이 가구 판매장을 위한 프로그램을 만들고 있다고 가정합시다. 당신의 코드는 다음을 나타내는 클래스들로 구성됩니다:
- 관련 제품들로 형성된 패밀리(제품군), 예: Chair(의자) + Sofa(소파) + CoffeeTable(커피 테이블).
- 해당 제품군의 여러 가지 변형. 예를 들어 Chair(의자) + Sofa(소파) + CoffeeTable(커피 테이블) 같은 제품들은 Modern(현대식), Victorian(빅토리안), ArtDeco(아르데코 양식)와 같은 변형으로 제공됩니다.

제품 패밀리들과 그들의 변형들
이제 당신이 새로운 개별 가구 객체를 생성했을 때, 이 객체들이 기존의 같은 패밀리 내에 있는 다른 가구 객체들과 일치하는 변형(스타일)을 가지도록 할 방법이 필요합니다. 그 이유는 당신의 고객이 스타일이 일치하지 않는 가구 세트를 받으면 크게 실망할 수 있기 때문입니다.

현대식 스타일의 소파는 빅토리안 스타일의 의자들과 잘 어울리지 않습니다.
또, 가구 공급업체들은 카탈로그를 매우 자주 변경하기 때문에, 그들은 새로운 제품 또는 제품군(패밀리)을 추가할 때마다 기존 코드를 변경해야 하는 번거로움을 피하고 싶을 것입니다.
해결책
추상 공장 패턴의 첫 번째 방안은 각 제품 패밀리(제품군)에 해당하는 개별적인 인터페이스를 명시적으로 선언하는 것입니다. (예: 의자, 소파 또는 커피 테이블). 그다음, 제품의 모든 변형이 위 인터페이스를 따르도록 합니다. 예를 들어, 모든 의자의 변형들은 Chair(의자) 인터페이스를 구현한다; 모든 커피 테이블 변형들은 CoffeeTable(커피 테이블) 인터페이스를 구현한다, 등의 규칙을 명시합니다.

같은 객체의 모든 변형은 단일 클래스 계층구조로 옮겨져야 합니다.
추상 공장 패턴의 다음 단계는 추상 팩토리 패턴을 선언하는 것입니다. 추상 공장 패턴은 제품 패밀리 내의 모든 개별 제품들의 생성 메서드들이 목록화되어있는 인터페이스입니다. (예: createChair(의자 생성), createSofa(소파 생성), createCoffeeTable(커피 테이블 생성) 등).

각 구상 팩토리는 특정 제품의 변형에 해당합니다.
다음은 제품 변형을 다룰 차례입니다. 제품 패밀리의 각 변형에 대해 AbstractFactory 추상 팩토리 인터페이스를 기반으로 별도의 팩토리 클래스를 생성합니다. 팩토리는 특정 종류의 제품을 반환하는 클래스입니다. 예를 들어 ModernFurnitureFactory(현대식 가구 팩토리)에서는 다음 객체들만 생성할 수 있습니다: ModernChair(현대식 의자), ModernSofa(현대식 소파) 및 ModernCoffeeTable(현대식 커피 테이블).
클라이언트 코드는 자신에 해당하는 추상 인터페이스를 통해 팩토리들과 제품들 모두와 함께 작동해야 합니다. 그래야 클라이언트 코드에 넘기는 팩토리의 종류와 제품 변형들을 클라이언트 코드를 손상하지 않으며 자유자재로 변경할 수 있습니다.

클라이언트들은 함께 작업하는 팩토리의 구상 클래스에 대해 신경을 쓰지 않아도 돼야 합니다.
클라이언트가 팩토리에 의자를 주문했다고 가정해 봅시다. 클라이언트는 팩토리의 클래스들을 알 필요가 없으며, 팩토리가 어떤 변형의 의자를 생성할지 전혀 신경을 쓰지 않습니다. 클라이언트는 추상 Chair(의자) 인터페이스를 사용하여, 현대식 의자이든 빅토리아식 의자이든 종류에 상관없이 모든 의자를 항상 동일한 방식으로 주문하며, 그가 의자에 대해 아는 유일한 사실은 제품이 sitOn(앉을 수 있다) 메서드를 구현한다는 것뿐입니다. 그러나, 생성된 의자의 변형은 항상 같은 팩토리 객체에서 생성된 소파 또는 커피 테이블의 변형과 같을 것입니다.
여기에서 명확히 짚고 넘어가야 할 점이 있습니다. 클라이언트가 추상 인터페이스에만 노출된다면 실제 팩토리 객체를 생성하는 것은 무엇일까요? 일반적으로 프로그램은 초기화 단계에서 구상 팩토리 객체를 생성합니다. 그 직전에 프로그램은 환경 또는 구성 설정에 따라 팩토리 유형을 선택해야 합니다.
[디자인패턴 with C++] Factory Pattern (추상 팩토리, 함수형 팩토리)
팩토리 메서드나 내부 팩토리 방식은 객체 한 종류를 생성하는 경우에 유용하다. 만약 여러 종류의 연관된 객체들을 생성해야 할 경우는 떻게 해야할까? 바로 추상 팩토리 방식을 사용하면 된다
velog.io
/**
* The Product interface declares the operations that all concrete products must
* implement.
*/
class Product {
public:
virtual ~Product() {}
virtual std::string Operation() const = 0;
};
/**
* Concrete Products provide various implementations of the Product interface.
*/
class ConcreteProduct1 : public Product {
public:
std::string Operation() const override {
return "{Result of the ConcreteProduct1}";
}
};
class ConcreteProduct2 : public Product {
public:
std::string Operation() const override {
return "{Result of the ConcreteProduct2}";
}
};
/**
* The Creator class declares the factory method that is supposed to return an
* object of a Product class. The Creator's subclasses usually provide the
* implementation of this method.
*/
class Creator {
/**
* Note that the Creator may also provide some default implementation of the
* factory method.
*/
public:
virtual ~Creator(){};
virtual Product* FactoryMethod() const = 0;
/**
* Also note that, despite its name, the Creator's primary responsibility is
* not creating products. Usually, it contains some core business logic that
* relies on Product objects, returned by the factory method. Subclasses can
* indirectly change that business logic by overriding the factory method and
* returning a different type of product from it.
*/
std::string SomeOperation() const {
// Call the factory method to create a Product object.
Product* product = this->FactoryMethod();
// Now, use the product.
std::string result = "Creator: The same creator's code has just worked with " + product->Operation();
delete product;
return result;
}
};
/**
* Concrete Creators override the factory method in order to change the
* resulting product's type.
*/
class ConcreteCreator1 : public Creator {
/**
* Note that the signature of the method still uses the abstract product type,
* even though the concrete product is actually returned from the method. This
* way the Creator can stay independent of concrete product classes.
*/
public:
Product* FactoryMethod() const override {
return new ConcreteProduct1();
}
};
class ConcreteCreator2 : public Creator {
public:
Product* FactoryMethod() const override {
return new ConcreteProduct2();
}
};
/**
* The client code works with an instance of a concrete creator, albeit through
* its base interface. As long as the client keeps working with the creator via
* the base interface, you can pass it any creator's subclass.
*/
void ClientCode(const Creator& creator) {
// ...
std::cout << "Client: I'm not aware of the creator's class, but it still works.\n"
<< creator.SomeOperation() << std::endl;
// ...
}
/**
* The Application picks a creator's type depending on the configuration or
* environment.
*/
int main() {
std::cout << "App: Launched with the ConcreteCreator1.\n";
Creator* creator = new ConcreteCreator1();
ClientCode(*creator);
std::cout << std::endl;
std::cout << "App: Launched with the ConcreteCreator2.\n";
Creator* creator2 = new ConcreteCreator2();
ClientCode(*creator2);
delete creator;
delete creator2;
return 0;
}