개발하는 리프터 꽃게맨입니다.

[C++/디자인패턴] 팩토리 메소드 패턴 본문

기타 개발 지식/디자인패턴

[C++/디자인패턴] 팩토리 메소드 패턴

파워꽃게맨 2024. 1. 27. 19:30

개요

팩토리 패턴은 객체를 생성하기 위한 디자인 패턴으로, 이러한 종류의 디자인 패턴을 생성 패턴이라고 합니다.

말 그대로, 객체를 생성하는 역할을 담당하고 있는 클래스, 함수를 만든다라고 보시면 되겠습니다.

 

C++ 코딩에서는 메모리 관리를 개발자 스스로 해야하기 때문에,

객체의 생성을 아무데서나 하기는 부담스럽습니다.

더하여, 생성자를 여기저기서 호출할 경우 유지보수에도 별로 좋은 코드는 아니죠.

그래서 생성만을 담당하는 팩토리 객체를 만들면

누가 언제 어디서 객체를 생성하는지 알 수 있고 관리도 쉬워집니다.

 

팩토리 패턴에는 팩토리 메서드 패턴, 가상 팩토리 패턴이 있습니다.

 

이번 포스팅에서는 팩토리 메서드 패턴만 다루도록 하겠습니다.

 


팩토리 메서드 패턴

1) 개요

 

팩토리 메서브 패턴은 부모 클래스에서 객체를 생성할 수 있는 인터페이스를 제공하고, 자식 클래스에서 생성될 객체의 유형을 재정의할 수 있도록 하는 생성 패턴입니다.

 

즉, 슈퍼 클래스로 팩토리 인터페이스를 만들고

하위 팩토리에선 인터페이스를 재정의하여 구체화합니다.

 

2) 기본구조

 

Weapon, Food, Armor 는 Items라는 상위 클래스로 관리되고 있습니다.

Item Factory Interface는 Items를 만들어내는 공장의 틀만 잡아둔 것이고

실제 구체적인 구현은 Weapon 공장, Food 공장, Armor 공장에서 수행합니다.

 

만약 Weapon을 만들고 싶으면 Weapon 공장에게 의뢰를 하면 될 것이고,

Food를 만들고 싶으면 Food 공장에 의뢰를 하면 될 것입니다.

 

3) 예시

아래 코드는 위 예시처럼

Weapon, Food, Armor 라는 Items가 있고

그것을 생성하는 팩토리 패턴을 보여줍니다.

 

 

팩토리 인터페이스입니다.

이 인터페이스는 하위 팩토리가 어떤 식으로 동작해야 하는지 행동을 정의합니다.

Item 에 대한 팩토리면 weapon 팩토리, food 팩토리, armor 팩토리 등등.. 아이템도 종류가 많을테니

팩토리에도 종류가 많겠죠?

 

그런 팩토리를 한 번에 묶어주는 팩토리 슈퍼 클래스라고 할 수 있겠습니다.

 

 

세부구현을 하고있는 Weapon 팩토리의 모습입니다.

무기의 이름을 WeaponName이라는 enum으로 관리하고 있고,

생성할 수 있는 무기를 해쉬맵으로 저장하고 있습니다.

 

특정 무기를 생성할 수 있는지 해쉬맵을 탐색해서 확인한 뒤

적절한 생성자를 호출하는 방식으로 동작하고 있습니다.

 

 

참고로 아이템의 모습이 위와 같습니다.

 

 

실제로 사용하는 모습입니다.

 

더보기
#include <iostream>
#include <unordered_map>
#include <cassert>
using namespace std;

/* 아이템 종류 */
enum class WeaponName { Sword = 0, Spear, Bow, WeaponCnt };
enum class FoodName {Apple = 0, Orange, Chicken, FoodCnt};
enum class ArmorName {Leggings = 0, Helmet, Plate, ArmorCnt};

/* 아이템 클래스 */
class Items { public: virtual ~Items() {} };

class Weapon : public Items { public: virtual ~Weapon() {} };
class Sword : public Weapon { public: virtual ~Sword() {} };
class Spear : public Weapon { public: virtual ~Spear() {} };
class Bow : public Weapon { public: virtual ~Bow() {} };

class Food : public Items { public: virtual ~Food() {} };
class Apple : public Food { public: virtual ~Apple() {} };
class Orange : public Food { public: virtual ~Orange() {} };
class Chicken : public Food { public: virtual ~Chicken() {} };

class Armor : public Items { public: virtual ~Armor() {} };
class Leggings : public Armor { public: virtual ~Leggings() {} };
class Helmet : public Armor { public: virtual ~Helmet() {} };
class Plate : public Armor { public: virtual ~Plate() {} };

/* 팩토리 인터페이스 */
class ItemFactory
{
public:
	Items* Creator(string Itemname)
	{
		return ItemCreator(Itemname);
	}

private:
	virtual void InitStorage() = 0;
	virtual Items* ItemCreator(string itemname) = 0;
};

/* 각각 무기, 음식, 아머 팩토리 */
class WeaponFactory : public ItemFactory
{
public:
	WeaponFactory() { InitStorage(); }

	Items* ItemCreator(string itemname) override
	{
		const auto& it = _storage[itemname];

		switch (it)
		{
		case WeaponName::Bow:
			return new Bow();
		case WeaponName::Spear:
			return new Spear();
		case WeaponName::Sword:
			return new Sword();
		}
	}

private:
	void InitStorage() override
	{
		WeaponName::WeaponCnt;

		InsertStorage("Bow", WeaponName::Bow);
		InsertStorage("Spear", WeaponName::Spear);
		InsertStorage("Sword", WeaponName::Sword);
	}

	inline void InsertStorage(string name1, WeaponName name2)
	{
		_storage.insert(make_pair(name1, name2));
	}

private:
	/*팩토리에서 생성할 수 있는 객체를 저장하고 있는 자료구조*/
	unordered_map<string, WeaponName> _storage;
};

class FoodFactory : public ItemFactory
{
public:
	FoodFactory() { InitStorage(); }

	Items* ItemCreator(string itemname) override
	{
		const auto& it = _storage[itemname];

		switch (it)
		{
		case FoodName::Apple:
			return new Apple();
		case FoodName::Chicken:
			return new Chicken();
		case FoodName::Orange:
			return new Orange();
		}
	}

private:
	void InitStorage() override
	{
		FoodName::FoodCnt;

		InsertStorage("Apple", FoodName::Apple);
		InsertStorage("Chicken", FoodName::Chicken);
		InsertStorage("Orange", FoodName::Orange);
	}

	inline void InsertStorage(string name1, FoodName name2)
	{
		_storage.insert(make_pair(name1, name2));
	}

private:
	/*팩토리에서 생성할 수 있는 객체를 저장하고 있는 자료구조*/
	unordered_map<string, FoodName> _storage;
};

class ArmorFactory : public ItemFactory
{
public:
	ArmorFactory() { InitStorage(); }

	Items* ItemCreator(string itemname) override
	{
		const auto& it = _storage[itemname];

		switch (it)
		{
		case ArmorName::Helmet:
			return new Helmet();
		case ArmorName::Leggings:
			return new Leggings();
		case ArmorName::Plate:
			return new Plate();
		}
	}

private:
	void InitStorage() override
	{
		ArmorName::ArmorCnt;

		InsertStorage("Helmet", ArmorName::Helmet);
		InsertStorage("Leggings", ArmorName::Leggings);
		InsertStorage("Plate", ArmorName::Plate);
	}

	inline void InsertStorage(string name1, ArmorName name2)
	{
		_storage.insert(make_pair(name1, name2));
	}

private:
	/*팩토리에서 생성할 수 있는 객체를 저장하고 있는 자료구조*/
	unordered_map<string, ArmorName> _storage;
};

int main()
{
	vector<Items*> Inventory;
	unordered_map<string, ItemFactory*> itemFactories;
	itemFactories["WeaponFactory"] = new WeaponFactory;
	itemFactories["FoodFactory"] = new FoodFactory;
	itemFactories["ArmorFactory"] = new ArmorFactory;

	Inventory.push_back(itemFactories["WeaponFactory"]->Creator("Sword"));
	Inventory.push_back(itemFactories["WeaponFactory"]->Creator("Spear2"));

	Inventory.push_back(itemFactories["FoodFactory"]->Creator("Chicken"));

	Inventory.push_back(itemFactories["ArmorFactory"]->Creator("Plate"));
}

 

4) 기타

Factory class의 객체는 굳이 2개 이상 필요하지 않습니다.

-> 그렇기에 싱글톤이라 static 메소드로 구현하는게 좋을듯 합니다.

 

5) 장점과 단점 

 

장점

(1) 단일 책임 원칙

생성자는 파라미터가 들어가면 굉장히 복잡해지고, 이 또한 큰 작업이 될 수 있습니다.

만약 플레이어, 아이템, 배경 등.. 맵에 생성하려고 하면 단일 책임의 원칙이 망가질 수 있습니다.

그렇기 때문에 생성에 대한 기능을 따로 빼서 관리한다는 것이 OOP의 설계 원칙과 부합한다고 말할 수 잇습니다.

 

(2) 개방-폐쇄 원칙

기존 코드를 훼손하지 않고, 새로운 팩토리가 필요하다면 클래스를 하나 더 만들면 됩니다.

확장성이 존재하는 디자인 패턴이라고 할 수 있습니다.

 

단점

(1) 패턴을 구현하기 위해 많은 클래스를 만들어야하기에

코드가 더 복잡해질 수 있으며, 코드의 길이가 더 길어질수도 있습니다.