패턴에 대해 공부하다 보면 팩토리 패턴에 대해 자주 나옵니다. 그만큼 유용하고, 많이 쓰이는 패턴이죠. 팩토리 패턴의 기본 개념은 아래와 같습니다.

enum CLASS_TYPE
{
	CLASS_FOO,
	CLASS_BAR
};

IBaseObject* CFactory::CreateInstance(CLASS_TYPE classType)
{
	switch (classType)
	{
		case CLASS_FOO:
			return new CFoo();
		case CLASS_BAR:
			return new CBar();
	}
	return nullptr;
}


팩토리 패턴을 사용하게 되면 아래와 같은 장점이 있습니다.

  • 객체의 추상화 단계가 높아진다
  • 객체 생성이 팩토리 메소드에 캡슐화가 이루어진다.
  • 사용자는 생성하는 객체에 대해 알필요가 없어진다.
  • 객체 추가 삭제가 용이해진다

다만, 위와 같은 형태의 팩토리 메소드에는 몇 가지 단점이 있습니다. 일단, 객체가 추가 되거나 삭제 될때마다 코드를 변경 해주어야합니다. enum 값을 추가하고, 팩토리 메소드 안에 새로운 new 코드를 추가해야 하죠. 또 한가지 치명적인 문제가 해당 소스 파일에서 생성하려고 하는 객체의 선언을 알고 있어야 합니다. 팩토리 메소드에서 생성할수 있는 객체의 헤더를 모두 include 해놔야 하죠.

이런 문제점을 해결한 팩토리 클래스를 만들어보겠습니다.
/////////////////////////////////// Factory.h
class IBaseObject;
class CFactory
{
public:
	// 팩토리 클래스는 싱글톤으로 제작
	static CFactory* Instance();
	// 객체 생성 함수(팩토리 메소드)
	IBaseObject* CreateInstance(std::string name);
	// 객체 생성 람다 등록 함수
	void RegisterFactoryFunction(std::string name, std::function<IBaseObject*(void)> classFactoryFunction);
private:
	// 객체 생성 람다가 저장될 컨테이너 타입
	using FactoryRegistry = std::unordered_map<std::string, std::function<IBaseObject*(void)>>;
	FactoryRegistry mFactoryRegistry;
};

// 객체 생성 람다를 팩토리에 등록시켜주는 템플릿 클래스
template<class T>
class CRegistrar {
public:
	CRegistrar(std::string className)
	{
		CFactory::Instance()->RegisterFactoryFunction(className,
			[](void) -> IBaseObject* { return new T(); });
	}
};

// 객체를 팩토리에 등록 시켜주는 매크로
#define REGISTER_CLASS(NAME, TYPE) \
    CRegistrar<TYPE> registrar_##TYPE(NAME);

/////////////////////////////////// Factory.cpp
CFactory* CFactory::Instance()
{
	static CFactory factory;
	return &factory;
}

IBaseObject* CFactory::CreateInstance(std::string name)
{
	auto it = mFactoryRegistry.find(name);
	if (it != mFactoryRegistry.end())
		return it->second();
	return nullptr;
}

void CFactory::RegisterFactoryFunction(std::string name, std::function<IBaseObject*(void)> factoryFunc)
{
	mFactoryRegistry[name] = factoryFunc;
}

유연한 객체 등록을 위해 enum아 아닌 문자열을 사용합니다. 그리고 unodered_map 컨테이너를 이용해 객체를 생성 해주는 람다를 저장합니다. CreateInstance 함수를 통해 객체의 이름을 검색하여, 해당 객체 생성 람다를 호출하게 됩니다.


실제 사용 예제는 아래와 같습니다.

/////////////////////////////////// Object.h
class CFoo : public IBaseObject
 {
 public:
 	virtual void Print() override;
 };

 class CBar : public IBaseObject
 {
 public:
 	virtual void Print() override;
 };

/////////////////////////////////// Object.cpp
 void CFoo::Print()
 {
 	std::cout << "This is Foo" << std::endl;
 }
REGISTER_CLASS("foo", CFoo);
 
 void CBar::Print()
 {
 	std::cout << "This is Bar" << std::endl;
 }
REGISTER_CLASS("bar", CBar);

/////////////////////////////////// main.cpp
int _tmain(int argc, _TCHAR* argv[])
{
	// 해당 객체 헤더를 include 할 필요 없다.
	auto one = CFactory::Instance()->CreateInstance("foo");
	auto two = CFactory::Instance()->CreateInstance("bar");

	one->Print();
	two->Print();

	return 0;
}

새로운 객체를 만들면서 팩토리 클래스는 전혀 수정할 필요 없습니다. 팩토리 클래스에 Object.h 헤더를 추가 할 필요도 없습니다. 등록 매크로를 통해 자동으로 객체의 이름과 타입이 팩토리 클래스에 등록이 되고, 객체를 생성하고, 사용 하는 곳에서도 객체의 형태를 전혀 모르는 상태에서 객체의 이름만 알면 생성 및 사용이 가능해졌습니다.

+ Recent posts