전략 패턴이란, 유사한 행위(알고리즘)를 수행하는 여러 전략들에 대해 공통의 인터페이스를 정의해두고, 각각의 구체적인 전략에 대한 클래스로 캡슐화하고, 언제든 동적으로 전략을 교체할 수 있도록하는 디자인패턴
Strategy 인터페이스 : Difficulty
Concrete Strategy : DifficultyEasy,DifficultyNormal,DifficultyHard
Context : EnemyPool
#include <iostream> #include <vector> #include <memory> #include <string> class Difficulty; // 게임의 난이도 정책을 정의하는 인터페이스 (Strategy Interface) class Difficulty { public: virtual ~Difficulty() = default; virtual int GetSpawnTime() const = 0; virtual std::string GetName() const = 0; }; // 구체적인 난이도: 쉬움 (Concrete Strategy) class DifficultyEasy : public Difficulty { public: int GetSpawnTime() const override { return 100 * 10; } std::string GetName() const override { return "Easy"; } }; // 구체적인 난이도: 보통 (Concrete Strategy) class DifficultyNormal : public Difficulty { public: int GetSpawnTime() const override { return 100 * 5; } std::string GetName() const override { return "Normal"; } }; // 구체적인 난이도: 어려움 (Concrete Strategy) class DifficultyHard : public Difficulty { public: int GetSpawnTime() const override { return 100 * 3; } std::string GetName() const override { return "Hard"; } }; // 몬스터의 기본 인터페이스 class Enemy { public: virtual ~Enemy() = default; virtual void Attack() = 0; virtual int GetAttack() const = 0; virtual void SetDifficulty(const Difficulty& _difficulty) = 0; virtual std::string GetName() const = 0; virtual bool IsDead() const = 0; virtual void Damage(int _damage) = 0; virtual bool IsActive() const = 0; virtual void Spawn() = 0; virtual void Dead() = 0; virtual void Update(int _time) = 0; }; // 구체적인 몬스터: 슬라임 class Slime : public Enemy { private: std::string name; const Difficulty* difficulty; int hp; int attack; bool active; int spawnTime; int time; public: Slime(const std::string& _name, const Difficulty* _stats) : name(_name), difficulty(_stats), hp(100), attack(10), active(false), spawnTime(difficulty->GetSpawnTime()), time(0) {} void Attack() override { if (!IsDead()) { std::cout << name << " 공격! " << attack << std::endl; active = false; } } int GetAttack() const override { return attack; } void SetDifficulty(const Difficulty& _difficulty) override { difficulty = &_difficulty; } std::string GetName() const override { return name; } bool IsDead() const override { return hp <= 0; } void Damage(int _damage) override { if (!IsDead()) { std::cout << name << " 데미지 " << _damage << " 받음" << std::endl; hp -= _damage; if (hp <= 0) { std::cout << name << " 사망" << std::endl; Dead(); } } } bool IsActive() const override { if (IsDead()) return false; if (hp <= 0) return false; if (!active) return false; return true; } void Spawn() override { std::cout << name << " 스폰" << std::endl; time = 0; hp = 100; active = true; } void Dead() override { active = false; } void Update(int _time) override { time += _time; if (time >= spawnTime) { Spawn(); } } }; // 몬스터 생성을 책임지는 팩토리 인터페이스 (Factory) class EnemyFactory { public: virtual ~EnemyFactory() = default; virtual std::unique_ptr<Enemy> Create(const std::string& _name, const Difficulty* _stats) = 0; }; // 슬라임 생성을 전담하는 구체적인 팩토리 (Concrete Factory) class EnemySlimeFactory : public EnemyFactory { public: std::unique_ptr<Enemy> Create(const std::string& _name, const Difficulty* _stats) override { return std::make_unique<Slime>(_name, _stats); } }; // 게임의 전체적인 로직을 관리하는 클래스 (Context) class GameHost { private: std::vector<std::unique_ptr<Enemy>> pool; std::unique_ptr<EnemyFactory> factory; std::unique_ptr<Difficulty> difficulty; int level; public: GameHost() { factory = std::make_unique<EnemySlimeFactory>(); difficulty = std::make_unique<DifficultyEasy>(); level = 1; } void CreateEnemy(int _poolsize) { pool.clear(); for (int i = 0; i < _poolsize; i++) { pool.push_back(factory->Create("슬라임", difficulty.get())); } } void ChangeDifficulty(std::unique_ptr<Difficulty> _difficulty) { difficulty = std::move(_difficulty); } int AttackToEnemy(int _damage) { for (const auto& enemy : pool) { if (enemy->IsActive()) { enemy->Damage(_damage); return enemy->GetAttack(); } } return NULL; } int ReturnEnemyCount() { int count = 0; for (const auto& enemy : pool) { if (!enemy->IsDead()) { count++; } } return count; } void CallAllDeadEnemy() { for (const auto& enemy : pool) { if (enemy->IsDead() && !enemy->IsActive()) { std::cout << enemy->GetName() << " 리스폰" << std::endl; enemy->Spawn(); } } } void PrintCurrentState() { int activeCount = 0; int deadCount = 0; for (const auto& enemy : pool) { if (enemy->IsActive()) { activeCount++; } else { deadCount++; } } std::cout << "현재 난이도: " << difficulty->GetName() << std::endl; std::cout << "활성화된 적: " << activeCount << std::endl; std::cout << "비활성화된 적: " << deadCount << std::endl; std::cout << "남은 적: " << ReturnEnemyCount() << std::endl; } void SetDifficultyToAllEnemy(std::unique_ptr<Difficulty> _newdifficulty) { ChangeDifficulty(std::move(_newdifficulty)); std::cout << "난이도 변경: " << difficulty->GetName() << std::endl; for (const auto& enemy : pool) { enemy->SetDifficulty(*difficulty); } } void DifficultyUpdate() { level++; if (level == 3) { SetDifficultyToAllEnemy(std::make_unique<DifficultyNormal>()); } } void Update(int _time) { std::cout << "시간: " << _time << std::endl; for (const auto& enemy : pool) { enemy->Update(_time); } } }; int main() { GameHost enemy; enemy.CreateEnemy(10); enemy.AttackToEnemy(100); enemy.AttackToEnemy(100); enemy.AttackToEnemy(100); enemy.CallAllDeadEnemy(); enemy.Update(10); enemy.Update(10); enemy.PrintCurrentState(); enemy.SetDifficultyToAllEnemy(std::make_unique<DifficultyHard>()); enemy.Update(10000); enemy.AttackToEnemy(100); enemy.PrintCurrentState(); return 0; }
- GameHost와 Difficulty의 관계 : GameHost는 Difficulty의 구체적인 클래스(DifficultyEasy 등)를 알지 못한다. 그저 difficulty 포인터를 통해 GetSpawnTime()과 같은 메서드를 호출할 뿐, 덕분에 ChangeDifficulty 메서드를 통해 언제든지 난이도 객체를 통째로 교체할 수 있다.