개발/Architecture & Design Pattern

C++ - 추상 팩토리 패턴 (Abstract Factory Pattern)

피로물든딸기 2024. 2. 14. 20:43
반응형

C, C++ 전체 링크

Architecture & Design Pattern 전체 링크

 

참고

- 팩토리 메서드 패턴

- 클래스 다이어그램 그리기

- 스마트 포인터 : unique_ptr

- friend로 private 멤버 출력하기

 

추상 팩토리 패턴 (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  
```
반응형