C++ - 추상 팩토리 패턴 (Abstract Factory Pattern)
Architecture & Design Pattern 전체 링크
참고
추상 팩토리 패턴 (Abstract Factory Pattern) - 생성 패턴
- 관련 있는 객체들의 생성을 추상화하여 객체 생성을 캡슐화하는 방법을 제공하는 패턴
- 클라이언트가 구체적인 클래스를 알지 못해도, 해당 클래스와 상호작용을 할 수 있도록 도와준다.
구현
- Abstract Factory : 관련된 객체들의 집합을 생성하기 위한 인터페이스를 정의
- Concrete Factory : Abstract Factory 인터페이스를 구현하여 실제로 객체를 생성
- Abstract Product : 팩토리가 생성하는 객체들의 인터페이스를 정의
- Concrete Product : 실제로 생성되는 객체들을 나타내는 클래스
장점
- SRP(단일 책임 원칙), 제품 생성 코드를 한 곳에서만 관리하여 유지보수가 쉽다.
- OCP(개방-폐쇄 원칙), 제품군을 추가/변경할 때, 클라이언트 코드를 수정하지 않고 팩토리만 수정하면 된다.
- 클라이언트는 구체적인 제품 클래스를 알 필요가 없으며, 팩토리를 통해 객체를 생성할 수 있다.
- 팩토리에서 생성되는 제품들은 상호 호환이 가능하다.
단점
- 새로운 제품군을 추가할 때마다 추상 팩토리, 구체적인 팩토리, 제품 클래스들을 모두 추가해야 한다.
또한, 인터페이스도 변경되어야 하므로 제품군이 자주 변경되는 경우 유연성이 떨어진다.
- 제품군이 복잡한 경우 클래스의 수가 급격히 증가한다.
팩토리 메서드 vs 추상 팩토리
팩토리 메서드 패턴과 추상 팩토리 패턴은 둘 다 객체 생성을 추상화하는 디자인 패턴이지만,
각각 목적과 구조에 조금 차이가 있다.
팩토리 메서드 | 추상 팩토리 |
|
특징 | 단일 객체 생성 | 서로 관련된 객체들의 집합을 생성 |
객체 생성 | 서브 클래스에서 상속을 통해 객체를 생성 | 인터페이스, 구성(composition)을 이용해 생성 |
구현 | 추상 메서드를 가지는 추상 클래스 정의 구체적인 구현은 서브 클래스에서 구현 |
추상 팩토리가 인터페이스를 정의 구체적인 팩토리가 구현하여 객체 생성 |
클라이언트 | 추상 클래스를 통해 객체 생성 | 팩토리 인터페이스를 통해 객체 생성 |
또한 추상 팩토리 패턴 내에서 사용하는 메서드가 팩토리 메서드인 경우도 가능하다.
재료 공장
팩토리 메서드 패턴에서 만든 빵에 재료를 첨가하여 조금 더 빵을 확장해 보자.
재료는 여러 가지가 있으므로 추상 팩토리 패턴을 적용해 보자.
재료들을 만들기 위해 추상 인터페이스를 제공하고, 구체적인 팩토리가 해당 메서드를 구현한다.
그리고 각 빵에는 재료들 : 과일, 크림, 견과류, 밀가루 등을 선택할 수 있도록 멤버 변수로 추가한다.
각 재료들도 마찬가지로 추상화된 인터페이스가 존재하며 각 메서드에서 구현된다.
추상 팩토리 패턴을 적용하기 위해 Bread를 수정하자.
해당 재료를 추가하기 위해 추상 메서드 addIngredients를 추가한다.
그리고 빵의 이름을 변경할 수 있는 setName 메서드도 추가하였다.
class Bread
{
public:
Bread() = default;
virtual ~Bread() = default;
virtual void addIngredients() = 0;
virtual void step1() const { cout << "step 1 of making : " << name << endl; }
virtual void step2() const { cout << "step 2 of making : " << name << endl; }
virtual void step3() const { cout << "step 3 of making : " << name << endl; }
string getName() const { return name; }
void setName(const string &n) { name = n; }
protected:
string name = "Bread";
unique_ptr<Fruit> fruit;
unique_ptr<Cream> cream;
// nuts, flour, ...
};
재료 중 하나인 과일은 다음과 같이 정의할 수 있다.
모든 과일은 Fruit을 상속 받고, 모든 크림은 Cream을 상속 받게 된다.
class Fruit
{
public:
virtual ~Fruit() = default;
virtual string toString() const = 0;
};
class Blueberry : public Fruit
{
public:
virtual string toString() const override { return "Blueberry"; }
};
class Pineapple : public Fruit
{
public:
virtual string toString() const override { return "Pineapple"; }
};
이제 재료를 첨가하는 재료 공장의 인터페이스를 아래와 같이 만들어 보자.
class IngredientsFactory
{
public:
virtual ~IngredientsFactory() = default;
virtual unique_ptr<Fruit> addFruit() = 0;
virtual unique_ptr<Cream> addCream() = 0;
// nuts, flour, ...
};
구체적인 공장, 예를 들어 파리바게뜨 공장은 과일을 파인애플을 사용하고, 크림은 치즈를 사용한다.
class ParisBaguetteIngredientsFactory : public IngredientsFactory
{
public:
unique_ptr<Fruit> addFruit() override { return make_unique<Pineapple>(); }
unique_ptr<Cream> addCream() override { return make_unique<Cheese>(); }
// nuts, flour, ...
};
이 공장은 파리바게뜨(빵 가게)에서 피자빵이나 머핀을 만들 때 필요하다.
class ParisBaguette : public BreadStore
{
protected:
unique_ptr<Bread> createBread(string name) override
{
unique_ptr<Bread> bread = nullptr;
unique_ptr<IngredientsFactory> ingredientsFactory
= make_unique<ParisBaguetteIngredientsFactory>();
if (name == "pizzabread")
{
bread = make_unique<PizzaBread>(move(ingredientsFactory));
bread->setName("ParisBaguette PizzaBread");
}
else if (name == "muffin")
{
bread = make_unique<Muffin>(move(ingredientsFactory));
bread->setName("ParisBaguette Muffin");
}
// or croissant, bagel, ...
else
return nullptr;
return bread;
}
};
피자빵은 과일과 크림이 모두 들어가기 때문에 addIngredients가 아래처럼 구현된다.
어떤 과일이나 크림이 들어가는지는 구체적인 재료 공장이 결정하게 된다.
class PizzaBread : public Bread
{
public:
PizzaBread() = default;
PizzaBread(unique_ptr<IngredientsFactory> factory)
: ingredientFactory(move(factory)) {}
void addIngredients() override
{
cout << "Add Ingredients" << name << endl;
fruit = ingredientFactory->addFruit();
cream = ingredientFactory->addCream();
}
private:
unique_ptr<IngredientsFactory> ingredientFactory = nullptr;
};
반대로 머핀에는 과일을 넣지 않기 때문에 아래처럼 구현할 수 있다.
즉, 필요한 재료를 넣기만 하거나 뺄 수도 있다.
class Muffin : public Bread
{
public:
Muffin() = default;
Muffin(unique_ptr<IngredientsFactory> factory)
: ingredientFactory(move(factory)) {}
void addIngredients() override
{
cout << "Add Ingredients" << name << endl;
// 머핀에는 과일을 뺀다.
// fruit = ingredientFactory->addFruit();
cream = ingredientFactory->addCream();
}
private:
unique_ptr<IngredientsFactory> ingredientFactory = nullptr;
};
마지막으로 생성된 빵의 정보를 얻기 위해 friend로 출력 메서드를 Bread에 추가하였다.
friend ostream& operator<<(ostream &os, const Bread* b)
{
os << "Name : " << b->getName() << endl;
if (b->fruit != nullptr) os << "Fruit : " << b->fruit->toString() << endl;
if (b->cream != nullptr) os << "Cream : " << b->cream->toString() << endl;
return os;
}
전체 코드는 다음과 같다.
#include <iostream>
#include <string>
#include <memory>
using namespace std;
/* ---------------------- Ingredients ---------------------- */
class Fruit
{
public:
virtual ~Fruit() = default;
virtual string toString() const = 0;
};
class Blueberry : public Fruit
{
public:
virtual string toString() const override { return "Blueberry"; }
};
class Pineapple : public Fruit
{
public:
virtual string toString() const override { return "Pineapple"; }
};
class Cream
{
public:
virtual ~Cream() = default;
virtual string toString() const = 0;
};
class Cheese : public Cream
{
public:
virtual string toString() const override { return "Cheese Cream"; }
};
class Chocolate : public Cream
{
public:
virtual string toString() const override { return "Chocolate Cream"; }
};
class IngredientsFactory
{
public:
virtual ~IngredientsFactory() = default;
virtual unique_ptr<Fruit> addFruit() = 0;
virtual unique_ptr<Cream> addCream() = 0;
// nuts, flour, ...
};
/* ------------------------- Bread ------------------------- */
class Bread
{
friend ostream& operator<<(ostream &os, const Bread* b)
{
os << "Name : " << b->getName() << endl;
if (b->fruit != nullptr) os << "Fruit : " << b->fruit->toString() << endl;
if (b->cream != nullptr) os << "Cream : " << b->cream->toString() << endl;
return os;
}
public:
Bread() = default;
virtual ~Bread() = default;
virtual void addIngredients() = 0;
virtual void step1() const { cout << "step 1 of making : " << name << endl; }
virtual void step2() const { cout << "step 2 of making : " << name << endl; }
virtual void step3() const { cout << "step 3 of making : " << name << endl; }
string getName() const { return name; }
void setName(const string &n) { name = n; }
protected:
string name = "Bread";
unique_ptr<Fruit> fruit;
unique_ptr<Cream> cream;
// nuts, flour, ...
};
class PizzaBread : public Bread
{
public:
PizzaBread() = default;
PizzaBread(unique_ptr<IngredientsFactory> factory)
: ingredientFactory(move(factory)) {}
void addIngredients() override
{
cout << "Add Ingredients" << name << endl;
fruit = ingredientFactory->addFruit();
cream = ingredientFactory->addCream();
}
private:
unique_ptr<IngredientsFactory> ingredientFactory = nullptr;
};
class Muffin : public Bread
{
public:
Muffin() = default;
Muffin(unique_ptr<IngredientsFactory> factory)
: ingredientFactory(move(factory)) {}
void addIngredients() override
{
cout << "Add Ingredients" << name << endl;
// 머핀에는 과일을 뺀다.
// fruit = ingredientFactory->addFruit();
cream = ingredientFactory->addCream();
}
private:
unique_ptr<IngredientsFactory> ingredientFactory = nullptr;
};
/* ------------------------- Store ------------------------- */
class BreadStore
{
public:
virtual ~BreadStore() = default;
unique_ptr<Bread> makeBread(string name)
{
unique_ptr<Bread> bread = createBread(name);
bread->addIngredients();
bread->step1();
bread->step2();
bread->step3();
return bread;
}
protected:
virtual unique_ptr<Bread> createBread(string name) = 0;
};
class ParisBaguetteIngredientsFactory : public IngredientsFactory
{
public:
unique_ptr<Fruit> addFruit() override { return make_unique<Pineapple>(); }
unique_ptr<Cream> addCream() override { return make_unique<Cheese>(); }
// nuts, flour, ...
};
class ParisBaguette : public BreadStore
{
protected:
unique_ptr<Bread> createBread(string name) override
{
unique_ptr<Bread> bread = nullptr;
unique_ptr<IngredientsFactory> ingredientsFactory
= make_unique<ParisBaguetteIngredientsFactory>();
if (name == "pizzabread")
{
bread = make_unique<PizzaBread>(move(ingredientsFactory));
bread->setName("ParisBaguette PizzaBread");
}
else if (name == "muffin")
{
bread = make_unique<Muffin>(move(ingredientsFactory));
bread->setName("ParisBaguette Muffin");
}
// or croissant, bagel, ...
else
return nullptr;
return bread;
}
};
class TouslesJoursIngredientsFactory : public IngredientsFactory
{
public:
unique_ptr<Fruit> addFruit() override { return make_unique<Blueberry>(); }
unique_ptr<Cream> addCream() override { return make_unique<Chocolate>(); }
// nuts, flour, ...
};
class TouslesJours : public BreadStore
{
protected:
unique_ptr<Bread> createBread(string name) override
{
unique_ptr<Bread> bread = nullptr;
unique_ptr<IngredientsFactory> ingredientsFactory
= make_unique<TouslesJoursIngredientsFactory>();
if (name == "pizzabread")
{
bread = make_unique<PizzaBread>(move(ingredientsFactory));
bread->setName("TouslesJours PizzaBread");
}
else if (name == "muffin")
{
bread = make_unique<Muffin>(move(ingredientsFactory));
bread->setName("TouslesJours Muffin");
}
// or croissant, bagel, ...
else
return nullptr;
return bread;
}
};
int main(void)
{
unique_ptr<BreadStore> parisBaguette = make_unique<ParisBaguette>();
unique_ptr<BreadStore> touslesJours = make_unique<TouslesJours>();
unique_ptr<Bread> bread1 = parisBaguette->makeBread("pizzabread");
cout << bread1.get() << endl;
bread1 = parisBaguette->makeBread("muffin");
cout << bread1.get() << endl;
unique_ptr<Bread> bread2 = touslesJours->makeBread("muffin");
cout << bread2.get() << endl;
bread2 = touslesJours->makeBread("pizzabread");
cout << bread2.get() << endl;
return 0;
}
ClassDiagram.md
```mermaid
classDiagram
class IngredientsFactory {
addFruit()*
addCream()*
addNuts()*
addFlour()*
}
class ParisBaguetteIngredientsFactory {
addFruit()
addCream()
addNuts()
addFlour()
}
class TouslesJoursIngredientsFactory {
addFruit()
addCream()
addNuts()
addFlour()
}
<<Interface>> IngredientsFactory
IngredientsFactory <|.. ParisBaguetteIngredientsFactory
IngredientsFactory <|.. TouslesJoursIngredientsFactory
class Fruit {
}
class Blueberry {
}
class Pineapple {
}
<<Interface>> Fruit
Fruit <|.. Blueberry
Fruit <|.. Pineapple
```