C++ 생성 패턴 — Factory·Builder·Singleton
생성 패턴 개요
섹션 제목: “생성 패턴 개요”생성 패턴(Creational Pattern)은 객체를 어떻게 만드는가에 관한 패턴입니다. new를 직접 호출하는 대신 생성 로직을 캡슐화해 코드의 유연성과 재사용성을 높입니다.
주요 생성 패턴:
- Factory Method: 서브클래스가 생성할 객체 타입을 결정
- Abstract Factory: 관련 객체 군(family)을 일관되게 생성
- Builder: 복잡한 객체를 단계적으로 구성
- Singleton: 인스턴스를 하나만 보장
Factory Method 패턴
섹션 제목: “Factory Method 패턴”생성할 객체의 타입을 서브클래스에 위임합니다.
// 제품 인터페이스class Enemy{public: virtual ~Enemy() = default; virtual void Attack() = 0; virtual std::string GetName() const = 0;};
// 구체 제품class Zombie : public Enemy{public: void Attack() override { std::cout << "Zombie bites!\n"; } std::string GetName() const override { return "Zombie"; }};
class Vampire : public Enemy{public: void Attack() override { std::cout << "Vampire drains blood!\n"; } std::string GetName() const override { return "Vampire"; }};
// 창조자 (Creator)class EnemySpawner{public: virtual ~EnemySpawner() = default;
// Factory Method virtual std::unique_ptr<Enemy> CreateEnemy() = 0;
// 템플릿 메서드: 생성 + 초기화 로직 std::unique_ptr<Enemy> SpawnEnemy() { auto enemy = CreateEnemy(); std::cout << enemy->GetName() << " spawned!\n"; return enemy; }};
class ZombieSpawner : public EnemySpawner{ std::unique_ptr<Enemy> CreateEnemy() override { return std::make_unique<Zombie>(); }};
class VampireSpawner : public EnemySpawner{ std::unique_ptr<Enemy> CreateEnemy() override { return std::make_unique<Vampire>(); }};
// 사용std::unique_ptr<EnemySpawner> spawner = std::make_unique<ZombieSpawner>();auto enemy = spawner->SpawnEnemy();enemy->Attack();Abstract Factory 패턴
섹션 제목: “Abstract Factory 패턴”관련 객체 군을 함께 생성합니다. 게임 테마(낮/밤) 또는 플랫폼별 UI 컴포넌트에 적합합니다.
// 추상 제품 인터페이스class Weapon { public: virtual void Use() = 0; virtual ~Weapon() = default; };class Armor { public: virtual void Equip() = 0; virtual ~Armor() = default; };
// 판타지 제품군class Sword : public Weapon { public: void Use() override { std::cout << "Slash!\n"; } };class Plate : public Armor { public: void Equip() override { std::cout << "Wear plate armor\n"; } };
// SF 제품군class Laser : public Weapon { public: void Use() override { std::cout << "Pew pew!\n"; } };class Shield : public Armor { public: void Equip() override { std::cout << "Raise shield\n"; } };
// 추상 팩토리class EquipmentFactory{public: virtual ~EquipmentFactory() = default; virtual std::unique_ptr<Weapon> CreateWeapon() = 0; virtual std::unique_ptr<Armor> CreateArmor() = 0;};
class FantasyFactory : public EquipmentFactory{public: std::unique_ptr<Weapon> CreateWeapon() override { return std::make_unique<Sword>(); } std::unique_ptr<Armor> CreateArmor() override { return std::make_unique<Plate>(); }};
class SciFiFactory : public EquipmentFactory{public: std::unique_ptr<Weapon> CreateWeapon() override { return std::make_unique<Laser>(); } std::unique_ptr<Armor> CreateArmor() override { return std::make_unique<Shield>(); }};
// 클라이언트: 팩토리 타입에 무관하게 동작void EquipCharacter(EquipmentFactory& factory){ auto weapon = factory.CreateWeapon(); auto armor = factory.CreateArmor(); weapon->Use(); armor->Equip();}
FantasyFactory fantasy;EquipCharacter(fantasy); // Slash! / Wear plate armorBuilder 패턴
섹션 제목: “Builder 패턴”복잡한 객체를 단계적으로 구성합니다. 생성자 매개변수가 많거나 조합이 다양할 때 사용합니다.
struct CharacterConfig{ std::string name; int hp = 100; int mp = 50; int attackPow = 10; float speed = 1.0f; bool hasMount = false; std::string weapon = "Fist";};
class CharacterBuilder{public: CharacterBuilder& SetName(const std::string& name) { _config.name = name; return *this; // 메서드 체인 }
CharacterBuilder& SetStats(int hp, int mp, int atk) { _config.hp = hp; _config.mp = mp; _config.attackPow = atk; return *this; }
CharacterBuilder& SetSpeed(float speed) { _config.speed = speed; return *this; }
CharacterBuilder& WithMount() { _config.hasMount = true; return *this; }
CharacterBuilder& WithWeapon(const std::string& weapon) { _config.weapon = weapon; return *this; }
CharacterConfig Build() { if (_config.name.empty()) throw std::invalid_argument("Character name is required"); return _config; }
private: CharacterConfig _config;};
// 사용auto hero = CharacterBuilder{} .SetName("Arthur") .SetStats(500, 200, 80) .SetSpeed(1.5f) .WithMount() .WithWeapon("Excalibur") .Build();Director 패턴과 결합
섹션 제목: “Director 패턴과 결합”class CharacterDirector{public: static CharacterConfig BuildWarrior(const std::string& name) { return CharacterBuilder{} .SetName(name) .SetStats(800, 50, 120) .WithWeapon("Great Sword") .Build(); }
static CharacterConfig BuildMage(const std::string& name) { return CharacterBuilder{} .SetName(name) .SetStats(300, 500, 40) .WithWeapon("Staff") .Build(); }};Singleton 패턴
섹션 제목: “Singleton 패턴”인스턴스를 하나만 보장합니다. 게임에서 GameManager, AudioManager, ResourceManager 등에 자주 사용됩니다.
스레드 안전 Singleton (C++11+)
섹션 제목: “스레드 안전 Singleton (C++11+)”class GameManager{public: // Meyers' Singleton: C++11에서 정적 지역 변수 초기화는 스레드 안전 static GameManager& GetInstance() { static GameManager instance; // 최초 호출 시 한 번만 초기화 return instance; }
void Initialize() { _initialized = true; } bool IsInitialized() const { return _initialized; }
// 복사·이동 금지 GameManager(const GameManager&) = delete; GameManager& operator=(const GameManager&) = delete;
private: GameManager() : _initialized(false) {} bool _initialized;};
// 사용GameManager::GetInstance().Initialize();CRTP 기반 재사용 가능한 Singleton
섹션 제목: “CRTP 기반 재사용 가능한 Singleton”template<typename T>class Singleton{public: static T& GetInstance() { static T instance; return instance; }
Singleton(const Singleton&) = delete; Singleton& operator=(const Singleton&) = delete;
protected: Singleton() = default; virtual ~Singleton() = default;};
// 실제 매니저class AudioManager : public Singleton<AudioManager>{ friend class Singleton<AudioManager>; // private 생성자 접근 허용
public: void PlaySound(const std::string& name) { std::cout << "Playing: " << name << "\n"; }
private: AudioManager() = default;};
AudioManager::GetInstance().PlaySound("explosion.wav");Singleton의 단점과 대안
섹션 제목: “Singleton의 단점과 대안”Singleton은 전역 상태를 만들어 테스트 격리가 어렵습니다. 의존성 주입(DI)으로 대체하는 것이 테스트 친화적입니다.
// 나쁜 예: Singleton 직접 의존class Player{ void Die() { AudioManager::GetInstance().PlaySound("die.wav"); // 테스트 불가 }};
// 좋은 예: 인터페이스 주입class IAudioService { public: virtual void PlaySound(const std::string&) = 0; };
class Player{public: explicit Player(IAudioService& audio) : _audio(audio) {}
void Die() { _audio.PlaySound("die.wav"); // 테스트 시 mock 주입 가능 }
private: IAudioService& _audio;};| 패턴 | 의도 | 사용 시점 |
|---|---|---|
| Factory Method | 서브클래스가 생성 타입 결정 | 생성할 클래스를 미리 알 수 없을 때 |
| Abstract Factory | 관련 객체 군 일관 생성 | 제품 패밀리를 교체해야 할 때 |
| Builder | 단계적 객체 구성 | 매개변수가 많거나 조합이 다양할 때 |
| Singleton | 인스턴스 1개 보장 | 전역 접근점이 필요할 때 (신중하게) |
생성 패턴은 new 호출을 한 곳에 모아 변경에 유연하게 만듭니다. 특히 Abstract Factory는 플랫폼이나 테마 전환 시 클라이언트 코드 수정 없이 객체 군 전체를 교체할 수 있어 강력합니다.