[C/C++] 팩토리 패턴(Factory Pattern)

할랑말랑·2026년 3월 20일

C/C++

목록 보기
33/45

팩토리 패턴(Factory Pattern)

객체 생성 로직을 별도의 클래스나 메서드로 분리하여, 클라이언트가 구체적인 클래스 타입을 알 필요 없이 객체를 생성할 수 있게 돕는 생성 패턴

1. 특징

  • 객체 생성의 추상화: new 연산자를 직접 호출하는 대신 팩토리 메서드를 통해 객체를 생성하여 결합도를 낮춘다
  • 유연성과 확장성: 새로운 클래스가 추가되어도 기존 클라이언트 코드를 수정할 필요 없이 팩토리 로직만 변경

2. 3가지 유형

  • Simple Factory
    가장 단순한 형태, 하나의 클래스에 static 메서드로 객체 생성, 조건문(if/switch)으로 타입 구분
  • Factory Method
    객체 생성을 서브클래스에 위임, 추상 클래스에 생성 메서드 정의, 각 서브클래스가 구체적인 객체 생성
  • Abstract Factory
    관련된 객체들의 집합을 생성, 여러 제품군을 다룸, 제품 패밀리 전체를 일관되게 생성(연관된 제품들의 군(Family)을 생성하기 위한 인터페이스를 제공)

3. 장점

  • 느슨한 결합 (Loose Coupling) : 클라이언트 코드가 구체 클래스에 의존하지 않음
  • 단일 책임 원칙 (SRP) : 객체 생성 로직을 한 곳에 집중
  • 개방-폐쇄 원칙 (OCP) : 새로운 타입 추가 시 기존 코드 수정 최소화

4. 단점

  • 클래스 증가 : Factory Method는 서브클래스 폭발적 증가 가능
  • 복잡도 증가
  • 간접 참조 : 직접 생성보다 한 단계 거침, 디버깅 시 추적 어려울 수 있음
  • Simple Factory의 한계 : 새 타입 추가 시 Factory 수정 필요, OCP 완전히 지키지 못함

Simple Factory + 오브젝트 풀 패턴에서 만든 예시(몬스터 스폰)

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에 주입하면, 풀의 코드를 전혀 수정하지 않고도 오크를 생성하는 풀을 만들 수 있게 된다.

0개의 댓글