객체 생성 로직을 별도의 클래스나 메서드로 분리하여, 클라이언트가 구체적인 클래스 타입을 알 필요 없이 객체를 생성할 수 있게 돕는 생성 패턴
EnemyFactory : 몬스터의 생성을 담당하는 클래스, 조건문(if/switch)으로 타입 구분 현재는 슬라임 한 타입
Enemy : 몬스터에서 필요한 기능을 인터페이스로 만듬
Slime : Enemy를 상속 구체적인 주체(슬라임)
#include <iostream> #include <vector> #include <string> class EnemyPool; class Slime; // 몬스터의 기본 인터페이스 class Enemy { public: Enemy() = default; virtual bool GetActive() const = 0; virtual void SetActive(bool _active) = 0; virtual void SetPositionInEnemyPool(EnemyPool* _pool) = 0; virtual std::string GetName() const = 0; virtual int GetHP() const = 0; virtual void Spawn() = 0; virtual void Hitted(int _damage) = 0; virtual bool CanBeAttacked() const = 0; virtual void Dead() = 0; virtual void Reset() = 0; }; // 구체적인 몬스터: 슬라임 class Slime : public Enemy { private: std::string name; int hp; bool active; EnemyPool* pool; bool isdead; public: Slime(const std::string& _name = "슬라임") : name(_name), pool(nullptr) { Reset(); } void Reset() override { hp = 100; isdead = false; active = false; } bool GetActive() const override { return active; } void SetActive(bool _active) { active = _active; } std::string GetName() const override { return name; } void SetPositionInEnemyPool(EnemyPool* _pool) { pool = _pool; } int GetHP() const override { return hp; } void Spawn() { std::cout << name << " 스폰" << std::endl; active = true; isdead = false; } void Hitted(int _damage) { if (CanBeAttacked()) { std::cout << _damage << " 데미지를 받음." << std::endl; hp -= _damage; if (hp <= 0) { std::cout << name << "의 hp가 " << hp << " 남음" << std::endl; Dead(); } else { // Dead(); // 원본 코드에 있던 부분인데, 아마도 버그인 것 같습니다. } } } bool CanBeAttacked() const override { if (!active) return false; if (hp <= 0) return false; if (isdead) return false; return true; } void Dead() override; // 선언만 하고 클래스 외부에서 정의 }; // 몬스터 생성을 전담하는 단순 팩토리 클래스 class EnemyFactory { public: static Enemy* CreateEnemy(const std::string& _type, const std::string& _name) { if (_type == "Slime") { return new Slime(_name); } return nullptr; } }; // 몬스터 객체들을 관리하는 오브젝트 풀 클래스 class EnemyPool { private: std::vector<Enemy*> pool; public: EnemyPool(int poolsize = 10) { for (int i = 0; i < poolsize; i++) { pool.push_back(EnemyFactory::CreateEnemy("Slime", "슬라임" + std::to_string(i))); } } ~EnemyPool() { for (int i = 0; i < pool.size(); i++) { delete pool[i]; } pool.clear(); } Enemy* GetObject() { for (Enemy* enemy : pool) { if (!enemy->GetActive()) { enemy->SetPositionInEnemyPool(this); enemy->SetActive(true); return enemy; } } return nullptr; } void Return(Enemy* enemy) { if (enemy == nullptr) return; enemy->SetActive(false); enemy->Reset(); } void CollectDeadEnemy() { for (const auto& enemy : pool) { if (!enemy->GetActive() && enemy->GetHP() <= 0) { std::cout << enemy->GetName() << " 수거" << std::endl; Return(const_cast<Enemy*>(enemy)); } } } void PrintStatus() const { int activeCount = 0; int deadCount = 0; for (Enemy* enemy : pool) { if (enemy->GetActive()) { if (enemy->GetHP() > 0) { activeCount++; } else { deadCount++; } } } std::cout << "==Pool Info==" << std::endl; std::cout << "Size : " << pool.size() << std::endl; std::cout << "Active : " << activeCount << std::endl; std::cout << "Dead : " << deadCount << std::endl; std::cout << "Inactive : " << pool.size() - activeCount - deadCount << std::endl; } }; // Slime::Dead() 메서드를 클래스 외부에서 정의 void Slime::Dead() { std::cout << name << " 사망" << std::endl; isdead = true; } int main() { EnemyPool pool(10); Enemy* enemy1 = pool.GetObject(); enemy1->Spawn(); pool.PrintStatus(); enemy1->Hitted(50); enemy1->Hitted(50); pool.CollectDeadEnemy(); pool.PrintStatus(); return 0; }
- 생성과 관리의 분리 : EnemyFactory는 객체 생성만을 책임지고, EnemyPool은 생성된 객체들의 수명 주기 관리와 재사용을 책임진다. 각 클래스의 역할이 명확하게 분리되어 있다.
- 상태 관리 : Slime 객체는 active, isdead와 같은 상태 변수를 통해 현재 자신이 풀에서 사용 중인지, 죽었는지, 아니면 비활성화 상태로 대기 중인지를 나타냈다. EnemyPool은 이 상태를 보고 객체를 빌려주거나 수거하는 결정한다.
Factory Method + 스마트 포인터
EnemyFactory : 부모 클래스 순수 가상함수 정의
EnemyFactoryA : 실제 객체 생성을 담당
#include <iostream> #include <vector> #include <string> #include <memory> class EnemyPool; class Slime; // 몬스터의 기본 인터페이스 (변경 없음) class Enemy { public: virtual ~Enemy() = default; virtual bool GetActive() const = 0; virtual void SetActive(bool _active) = 0; virtual void SetPool(EnemyPool* _pool) = 0; virtual std::string GetName() const = 0; virtual int GetHP() const = 0; virtual void Spawn() = 0; virtual void Hitted(int _damage) = 0; virtual bool CanBeAttacked() const = 0; virtual void Dead() = 0; virtual void Reset() = 0; }; // 구체적인 몬스터: 슬라임 (SetPool 메서드명 변경 외 큰 변화 없음) class Slime : public Enemy { private: std::string name; int hp; bool active; EnemyPool* pool; bool isdead; public: Slime(const std::string& _name = "슬라임") : name(_name), pool(nullptr) { Reset(); } void Reset() override { hp = 100; isdead = false; active = false; } bool GetActive() const override { return active; } void SetActive(bool _active) override { active = _active; } std::string GetName() const override { return name; } void SetPool(EnemyPool* _pool) override { pool = _pool; } int GetHP() const override { return hp; } void Spawn() override { std::cout << name << " 스폰" << std::endl; active = true; isdead = false; } void Hitted(int _damage) override { if (CanBeAttacked()) { std::cout << _damage << " 데미지를 받음." << std::endl; hp -= _damage; if (hp <= 0) { std::cout << name << "의 hp가 " << hp << " 남음" << std::endl; Dead(); } } } bool CanBeAttacked() const override { if (!active) return false; if (hp <= 0) return false; if (isdead) return false; return true; } void Dead() override; }; // 팩토리 메서드 패턴: 팩토리의 인터페이스 class EnemyFactory { public: virtual ~EnemyFactory() = default; virtual std::unique_ptr<Enemy> CreateEnemy(const std::string& _name) = 0; }; // 팩토리 메서드 패턴: 구체적인 팩토리 클래스 class EnemyFactoryA : public EnemyFactory { public: std::unique_ptr<Enemy> CreateEnemy(const std::string& _name) override { return std::make_unique<Slime>(_name); } }; // 오브젝트 풀 클래스 class EnemyPool { private: // 메모리 관리를 위해 스마트 포인터 사용 std::vector<std::unique_ptr<Enemy>> pool; std::unique_ptr<EnemyFactory> enemyF; public: EnemyPool(int poolsize = 10) { // 구체적인 팩토리를 주입받아 사용 enemyF = std::make_unique<EnemyFactoryA>(); for (int i = 0; i < poolsize; i++) { pool.push_back(enemyF->CreateEnemy("슬라임" + std::to_string(i))); } } // 스마트 포인터 덕분에 소멸자에서 수동 delete 필요 없음 ~EnemyPool() { pool.clear(); } // 객체 소유권은 풀에 두고, 사용권만 raw pointer로 빌려줌 Enemy* GetObject() { for (const auto& enemy : pool) { if (!enemy->GetActive()) { enemy->SetPool(this); enemy->SetActive(true); return enemy.get(); } } return nullptr; } void Return(Enemy* enemy) { if (enemy == nullptr) return; enemy->SetActive(false); enemy->Reset(); } void CollectDeadEnemy() { for (const auto& enemy : pool) { if (!enemy->GetActive() && enemy->GetHP() <= 0) { std::cout << enemy->GetName() << " 수거" << std::endl; Return(enemy.get()); } } } void PrintStatus() const { int activeCount = 0; int deadCount = 0; for (const auto& enemy : pool) { if (enemy.get()->GetActive()) { if (enemy.get()->GetHP() > 0) { activeCount++; } else { deadCount++; } } } std::cout << "==Pool Info==" << std::endl; std::cout << "Size : " << pool.size() << std::endl; std::cout << "Active : " << activeCount << std::endl; std::cout << "Dead : " << deadCount << std::endl; std::cout << "Inactive : " << (pool.size() - activeCount - deadCount) << std::endl; } }; void Slime::Dead() { std::cout << name << " 사망" << std::endl; isdead = true; } int main() { EnemyPool pool(10); Enemy* enemy1 = pool.GetObject(); enemy1->Spawn(); pool.PrintStatus(); enemy1->Hitted(50); enemy1->Hitted(50); pool.CollectDeadEnemy(); pool.PrintStatus(); return 0; }
- std::unique_ptr 도입 : EnemyPool의 pool이 std::vector<std::unique_ptr>로 변경, 이제 풀이 소멸될 때 벡터에 저장된 모든 몬스터 객체들이 자동으로 안전하게 삭제되므로, ~EnemyPool() 소멸자에서 수동으로 delete를 호출할 필요가 없어졌다.
- EnemyFactory가 추상 기본 클래스가 되고, 순수 가상 함수 CreateEnemy를 가진다.
- EnemyFactoryA가 EnemyFactory를 상속받아 Slime을 생성하는 구체적인 방법을 구현
- EnemyPool은 이제 EnemyFactoryA 같은 구체적인 팩토리에 의존하는 대신, EnemyFactory라는 추상 타입에 의존합니다. 이로 인해 나중에 Orc를 생성하는 EnemyFactoryB를 만들어 EnemyPool에 주입하면, 풀의 코드를 전혀 수정하지 않고도 오크를 생성하는 풀을 만들 수 있게 된다.