객체를 매번 새로 생성하고 파괴하는 대신, 미리 만들어 놓은 객체들을 '풀(Pool)'이라는 저장소에 보관해 두었다가 필요할 때 재사용하는 소프트웨어 생성 디자인 패턴
- Enemy : Concrete objec Pool에 적용할 객체
Spawn : 풀에서 받은 객체를 스폰하기 위한 메서드
Reset : 객체의 데이터 초기화를 위한 메서드
Hit : 공격받았을때 처리할 메서드
Dead : 체력이 0이하가되서 pool에 반납하기위한 메서드
- EnemyPool : 일정수의 Enemy객체를 관리할 객체
Return : 객체 사용후 반납 위한 메서드
GetObject : 사용 가능한 객체를 넘기는 메서드
PrintStatus : Pool 상태를 출력하기위한 메서드
CollectDeadEnemies : 사용후 회수가 안된 객체를 재사용하기위해 확인하는 메서드![]()
#include <iostream> #include <vector> #include <string> class EnemyPool; // 풀에서 관리될 객체 클래스 class Enemy { private: std::string name; int hp; bool active; EnemyPool* pool; // 자신을 관리하는 풀에 대한 포인터 bool isdead; public: Enemy(const std::string& _name) : name(_name), pool(nullptr) { Reset(); } void Reset() { hp = 100; isdead = false; active = false; } bool GetActive() const { return active; } void SetActive(const bool _active) { active = _active; } std::string GetName() const { return name; } void SetPool(EnemyPool* _pool) { pool = _pool; } int GetHP() const { return hp; } void Spawn() { std::cout << name << " 스폰" << std::endl; active = true; isdead = false; } void Hit(int _damage) { if (CanBeAttacked()) { std::cout << _damage << " 데미지를 받음." << std::endl; hp -= _damage; if (hp > 0) { std::cout << name << "의 hp : " << hp << " 남음" << std::endl; } else { Dead(); } } } bool CanBeAttacked() const { if (!active) return false; if (hp <= 0) return false; if (isdead) return false; return true; } void Dead(); }; // 오브젝트 풀 클래스 class EnemyPool { private: std::vector<Enemy*> pool; public: EnemyPool(int poolsize = 10) { for (int i = 0; i < poolsize; i++) { pool.push_back(new Enemy("슬라임" + 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->SetPool(this); enemy->SetActive(true); return enemy; } } return nullptr; } void Return(Enemy* enemy) { if (enemy == nullptr) return; enemy->SetActive(false); enemy->Reset(); } // 이 메서드는 이제 Enemy::Dead()에 의해 간접적으로 호출되므로, 외부에서 직접 호출할 필요가 줄어듦 void CollectDeadEnemies() { for (Enemy* enemy : pool) { if (!enemy->GetActive() && enemy->GetHP() <= 0) { std::cout << enemy->GetName() << " 수거" << std::endl; Return(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; } }; // Enemy::Dead() 메서드의 정의 void Enemy::Dead() { std::cout << name << " 사망" << std::endl; isdead = true; if (pool) { // 자신이 속한 풀에 스스로를 반납 pool->Return(this); } } int main() { EnemyPool pool(10); Enemy* enemy1 = pool.GetObject(); enemy1->Spawn(); pool.PrintStatus(); enemy1->Hit(50); enemy1->Hit(50); // 이 메서드 안에서 Dead()가 호출되고, Dead() 안에서 pool->Return()이 호출됨 pool.PrintStatus(); return 0; }
- 자기 반납(Self-Returning) 객체 : Enemy 객체는 더 이상 수동적인 데이터 덩어리가 아니라, 자신의 상태에 따라 능동적으로 풀과 상호작용하는 주체
- 강한 결합(Tight Coupling) : 이 방식은 코드를 단순화하고 객체의 행동을 명확하게 만들 수 있지만, Enemy 클래스가 EnemyPool 클래스에 직접적으로 의존하게 되는 단점이 있다. 즉, Enemy는 자신을 관리해 줄 EnemyPool 없이는 온전한 역할을 수행하기 어렵다.
- 책임의 이동 객체를 풀에 반납하는 책임이 풀 관리자(EnemyPool)에서 객체 자신(Enemy)에게로 일부 이동했다.