728x90

디자인 패턴을 따로 공부하지는 않았고(학부때 관련 전공필수 과목이 없었다...), 현업에서 개발을 하다가

 

접하게 되어 조금씩 생각날때 정리를 해보려한다. 들어가기 전에 기억해야할 것은

 

책에 나온 패턴들을 곧이곧대로 사용하려 하면 안된다. "만능, 치트키" 같은 물건이 아니며, 맹목적으로

 

그러한 설계(상속, 연관등의 구조)를 따를 필요는 없다. 개념을 이해하고 유연하게 바꾸어 사용하자.

 

어차피 곧이 곧대로 적용할 수 있을 정도로 프로덕트 코드들이 단순하지 않다.

(늘 문제는 생각하지 못한곳에서 발생하는 프로그래밍의 오묘한 세계...)

 

개요

먼저는 Manager 계열의 Class 설계시 아주 빈번하게 사용되고 있는 "생성" 패턴인 Factory에대해 알아보려 한다.

 

그중에 가장 단순한 형태인 Simple Factory가 이번 주제인데, 사실 Simple Factory라고 부르지만

 

실제 "패턴"으로 치지는 않는 아주 단순한 구조이다.

 

왜 사용할까?

구상 클래스를 중심으로 작성되는 코드는 결합도가 높아서 나중에 수정사항들이 생기면 "빈번한" 수정을 가해야한다.

 

그래서 결합도를 줄여 유지보수성을 높이기 위해 자주 사용된다.

 

예를들어 다음과 같은 코드가 있다.

class Monster
{
public:
	virtual void attack(void) = 0;
};

class MushroomMonster : public Monster
{
public:
	virtual void attack(void)
	{
		std::cout << "Mushroom attacked!" << std::endl;
	}
};

class SnailMonster : public Monster
{
public:
	virtual void attack(void)
	{
		std::cout << "Snail attacked!" << std::endl;
	}
};

int main(void)
{
	Monster* monster1 = new MushroomMonster();
	Monster* monster2 = new SnailMonster();

	monster1->attack();
	monster2->attack();

	/*
	  ...
	 */

	Monster* monster3 = new MushroomMonster();
	Monster* monster4 = new SnailMonster();

	monster1->attack();
	monster2->attack();

	return 0;
}

Monster class를 상속받는 버섯몬스터 클래스와 달팽이몬스터 클래스가 있고, 단순하게 new를 해서

 

부모 class의 pointer로 받아서 객체를 핸들링 하는 코드가 있다. 이때 기획이 바뀌어 몬스터들에게

 

"체력 게이지"라는 스펙을 넣게 되었다 하자. 모든 몬스터는 반드시 hp를 가져야 하므로, 

 

setter 메소드가 아닌, constructor에 강제하는것이 좋으므로 다음과 같이 변경했다.

class MushroomMonster : public Monster
{
public:
	MushroomMonster(int hp)
		: _hp(hp)
	{
	}

	virtual void attack(void)
	{
		std::cout << "Mushroom attacked!" << std::endl;
	}

private:
	int	_hp;
};

class SnailMonster : public Monster
{
public:
	SnailMonster(int hp)
		: _hp(hp)
	{
	}

	virtual void attack(void)
	{
		std::cout << "Snail attacked!" << std::endl;
	}

private:
	int	_hp;
};

# _hp를 Monster에 추가해줄 수 있지만, 여기서는 순수 interface의 역할만 수행하도록하여 넣지 않음.

 

이렇게 되면 main 함수에서 다음과 같이 수정해줘야 한다.

int main(void)
{
	Monster* monster1 = new MushroomMonster(10); // 수정
	Monster* monster2 = new SnailMonster(20); // 수정

	monster1->attack();
	monster2->attack();

	/*
	  ...
	 */

	Monster* monster3 = new MushroomMonster(10); // 수정
	Monster* monster4 = new SnailMonster(20); // 수정

	monster1->attack();
	monster2->attack();

	return 0;
}

new operator로 구상클래스를 인스턴스화 하고 있는 모든 부분에 수정이 가해진다. 의존도가 높아(결합도가 큼)

 

유지보수가 어려워진다. 생성만 담당하는 간단한 Factory로 결합도를 줄일 수 있다.

class MonsterFactory
{
public:
	enum class MonsterType : int
	{
		eMushroom = 0,
		eSnail,
		eCount
	};

	static Monster* createMonster(MonsterType type)
	{
		switch (type)
		{
		case MonsterType::eMushroom:
			return new MushroomMonster(10);
		case MonsterType::eSnail:
			return new SnailMonster(20);
		default:
			// assertion
			break;
		}
	}
};

int main(void)
{
	Monster* monster1 = MonsterFactory::createMonster(MonsterFactory::MonsterType::eMushroom);
	Monster* monster2 = MonsterFactory::createMonster(MonsterFactory::MonsterType::eSnail);

	monster1->attack();
	monster2->attack();

	/*
	  ...
	 */

	Monster* monster3 = MonsterFactory::createMonster(MonsterFactory::MonsterType::eMushroom);
	Monster* monster4 = MonsterFactory::createMonster(MonsterFactory::MonsterType::eSnail);

	monster1->attack();
	monster2->attack();

	return 0;
}

Factory 클래스는 주로 생성만 담당한다 하지만, 게임쪽에서 Manager란 클래스는 생성&관리 까지하여,

 

Simple Factory의 기능 + 라이프사이클관리(= 소유권 관리) 까지 포함하는 경우가 더러 있다.

 

이제 비지니스 로직에서 new의 직접 사용은 지양하자~

728x90

'프로그래밍 > C++' 카테고리의 다른 글

Prototype Pattern  (0) 2021.07.13
Adaptor Pattern  (0) 2021.06.30
[C++] Cyclic Reference - Weak Reference  (0) 2020.11.11
[C++] Reference Counting  (2) 2020.11.04
힙(Heap)과 완전 이진 트리(Complete binary tree)  (1) 2019.10.31

+ Recent posts