[C/C++] 전략 패턴(Strategy Pattern)

할랑말랑·2026년 3월 23일

C/C++

목록 보기
34/45

전략 패턴(Strategy Pattern)

전략 패턴이란, 유사한 행위(알고리즘)를 수행하는 여러 전략들에 대해 공통의 인터페이스를 정의해두고, 각각의 구체적인 전략에 대한 클래스로 캡슐화하고, 언제든 동적으로 전략을 교체할 수 있도록하는 디자인패턴

1. 특징

  • 알고리즘 캡슐화 : 각 알고리즘을 독립적인 클래스로 정의하여 관리
  • 동적 교체 : 실행 시점(Runtime)에 필요한 전략을 선택하여 컨텍스트 코드를 수정할 필요가 없다
  • 개방-폐쇄 원칙 준수 : 새로운 전략이 추가되어도 기존 컨텍스트 코드를 수정할 필요가 없다

2. 구성 요소

  • 전략 인터페이스(Strategy) : 알고리즘을 정의하는 공통 인터페이스, 특정 작업을 수행하기 위한 메서드를 선언(순수 가상 함수)
  • 구체적인 전략(Concrete Strategy) : Strategy 인터페이스를 구현하는 구체적인 알고리즘 클래스
  • 컨텍스트(Context) : 전략 객체를 담아 실행하는 역할

3. 장점

  • 유연한 확장성 (OCP 준수): 기존 코드를 건드리지 않고 새로운 알고리즘(전략)을 쉽게 추가
  • 공통 로직은 컨텍스트에 두고, 변하는 부분만 전략으로 분리하여 관리하므로 중복이 줄어든다.

4. 단점

  • 각 전략마다 새로운 클래스를 만들어야 하므로 관리해야 할 파일이 많아집니다.
  • 구조가 단순한 경우에는 오히려 설계를 복잡하게 만들 수 있습니다.

Strategy

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 메서드를 통해 언제든지 난이도 객체를 통째로 교체할 수 있다.

0개의 댓글