1. Abstract Factory
- 구체적인 Concrete Class를 지정하지 않고 공통된 테마를 가진 개별 팩토리를 캡슐화하는 방법
1.1. 의도
- 추상 팩토리는 관련 객체들의 Concrete Class들을 지정하지 않고도 객체들의 모음을 생성할 수 있도록 하는 생성 패턴
1.2. 문제
- 가구 판매장을 위한 프로그램을 만들면서 아래와 같은 제품들을 클래스로 구성을 가정할 수 있음
- 해당 제품군에서 여러 가지 조합으로 선택이 가능하며, 예를 들어 의자 + 소파 + 커피 테이블에서 종류별로 아르데코, 빅토리안, 현대식으로 제공할 수 있음
- 또한, 카탈로그를 자주 변경해야 하기 때문에 새로운 제품 및 종류를 추가할 때마다 기존 코드를 변경하는 것을 지양해야 함
1.3. 해결책
- 각 제품군을 개별적인 인터페이스로 선언 및 모든 변형을 인터페이스에 따르도록 함
- 다음으로 추상팩토리패턴을 선언하여 제품군 내의 모든 개별 제품의 생성 메소드들이 목록화되어 있는 인터페이스를 만듦
- 제품의 변형은 추상팩토리의 인터페이스를 기반으로 별도의 팩토리 클래스를 만들어서 특정 종류의 제품을 return하는 클래스를 가지게 함
- 클라이언트 코드는 자신에 해당하는 추상 인터페이스를 통해 팩토리들과 제품들과 함께 동작
2. 구조
- Abstract Product
- Concrete Product
- Abstract Product 를 기반으로 만든 세부적인 제품에 대한 구현
- Abstract Factory
- Abstract Product들을 생성하기 위한 생성 관련 메소드들을 선언
- Concrete Factory
- Abstract Factory를 기반으로 만든 세부적인 생성 메소드 구현
3. 사용
- 관련된 제품군을 다양한 묶음으로 동작해야 하지만 해당 제품들이 Concrete 클래스 들에 의존하고 싶지 않을 때 사용
- 이는 확장성에 좋음
- 추상팩토리는 제품군의 각 클래스에서부터 객체들을 생성할 수 있는 인터페이스를 제공
- 인터페이스를 통해 개체를 생성하면 이미 생성된 제품과 일치하지 않는 잘못된 제품 생성에 대해 걱정할 필요가 없음
4. Pros and Cons
4.1. Pros
- 팩토리로부터 만든 제품이 서로 호환되는지 확인 가능
- 제품과 클라이언트 코드 간의 tight coupling을 피할 수 있음
- Single Responsibility Principle
- Open/Closed Principle
4.2. Cons
- Code complexity, 패턴과 함께 새로운 인터페이스와 클래스들이 도입되기 때문에 코드가 복잡해질 수 있음
5. 코드로 알아보기
#include <iostream>
#include <string>
// Abstract 프로덕트
class AbstractProductA {
public:
virtual ~AbstractProductA(){};
// Concrete에서 구현을 위임하기 위해 순수가상함수로 선언
virtual std::string UsefulFunctionA() const = 0;
};
// Concrete 프로덕트
class ConcreteProductA1 : public AbstractProductA {
public:
// 세부 동작에 대한 메소드 구현
std::string UsefulFunctionA() const override {
return "The result of the product A1.";
}
};
class ConcreteProductA2 : public AbstractProductA {
// 세부 동작에 대한 메소드 구현
std::string UsefulFunctionA() const override {
return "The result of the product A2.";
}
};
class AbstractProductB {
public:
virtual ~AbstractProductB(){};
virtual std::string UsefulFunctionB() const = 0;
// AbstractProductA 과 호환하기 위해 도입된 함수
// 즉, 서로 다른 제품군의 기능을 사용할 수 있음
virtual std::string AnotherUsefulFunctionB(const AbstractProductA &collaborator) const = 0;
};
class ConcreteProductB1 : public AbstractProductB {
public:
std::string UsefulFunctionB() const override {
return "The result of the product B1.";
}
// AbstractProductA 제품들과 호환 가능하게 하는 함수 구현부
std::string AnotherUsefulFunctionB(const AbstractProductA &collaborator) const override {
const std::string result = collaborator.UsefulFunctionA();
return "The result of the B1 collaborating with ( " + result + " )";
}
};
class ConcreteProductB2 : public AbstractProductB {
public:
std::string UsefulFunctionB() const override {
return "The result of the product B2.";
}
std::string AnotherUsefulFunctionB(const AbstractProductA &collaborator) const override {
const std::string result = collaborator.UsefulFunctionA();
return "The result of the B2 collaborating with ( " + result + " )";
}
};
// Abstrace 팩토리 인터페이스
class AbstractFactory {
public:
// create 관련 순수 가상 함수
virtual AbstractProductA *CreateProductA() const = 0;
virtual AbstractProductB *CreateProductB() const = 0;
};
// Concrete class로 인터페이스에 대한 구현
class ConcreteFactory1 : public AbstractFactory {
public:
AbstractProductA *CreateProductA() const override {
// Product별로 같은 인터페이스를 공유하면 아래와 같이 서로 다른 객체를 만들 수 있음
return new ConcreteProductA1();
}
AbstractProductB *CreateProductB() const override {
return new ConcreteProductB1();
}
};
// Concrete 팩토리, 추상팩토리 인터페이스에 대한 구현
class ConcreteFactory2 : public AbstractFactory {
public:
AbstractProductA *CreateProductA() const override {
// Product별로 같은 인터페이스를 공유하면 아래와 같이 서로 다른 객체를 만들 수 있음
return new ConcreteProductA2();
}
AbstractProductB *CreateProductB() const override {
return new ConcreteProductB2();
}
};
// 클라이언트 코드
void ClientCode(const AbstractFactory &factory) {
const AbstractProductA *product_a = factory.CreateProductA();
const AbstractProductB *product_b = factory.CreateProductB();
std::cout << product_b->UsefulFunctionB() << "\n";
std::cout << product_b->AnotherUsefulFunctionB(*product_a) << "\n";
delete product_a;
delete product_b;
}
int main() {
std::cout << "Client: Testing client code with the first factory type:\n";
ConcreteFactory1 *f1 = new ConcreteFactory1();
ClientCode(*f1);
delete f1;
std::cout << std::endl;
std::cout << "Client: Testing the same client code with the second factory type:\n";
ConcreteFactory2 *f2 = new ConcreteFactory2();
ClientCode(*f2);
delete f2;
return 0;
}
참고
wikipedia
refactoring.guru
This is personal diary for study documents.
Please comment if I'm wrong or missing something else 😄.
Top
댓글남기기