메인 콘텐츠로 건너뛰기

창조의 권한을 위임하다: C++ 팩토리 메서드 패턴

팩토리 메서드 패턴의 핵심은 “객체 생성을 서브클래스에 위임하는 것”입니다. 상위 클래스는 어떤 객체가 만들어질지 정확히 모른 채, 단지 "무언가 만들어져야 한다"는 추상적인 명령만 내립니다.

2025년 12월 30일
c++
창조의 권한을 위임하다: C++ 팩토리 메서드 패턴 - 아티클 커버 이미지

인류의 역사는 '도구의 표준화'와 함께 비약적으로 발전했습니다. 산업혁명 이전의 장인들은 칼 한 자루, 구두 한 켤레를 만들기 위해 모든 공정을 스스로 책임져야 했죠. 하지만 수요가 폭발하고 시스템이 복잡해지면서, 우리는 '만드는 자'와 '사용하는 자'를 분리하기 시작했습니다. 표준화된 공정, 즉 팩토리(Factory)의 탄생입니다.

소프트웨어의 세계에서도 마찬가지입니다. 우리는 종종 new라는 키워드를 통해 객체를 직접 탄생시키곤 합니다. 하지만 구체적인 클래스 이름을 코드에 직접 박아넣는 행위는, 마치 특정 상표의 부품만 사용하도록 고정된 기계처럼 유연성을 잃게 만듭니다. 만약 내일 새로운 종류의 객체가 필요해진다면, 우리는 소스 코드의 수천 곳을 뒤져 new 뒤의 이름을 고쳐야 할지도 모릅니다. 이 '창조의 저주'를 풀기 위해, 우리는 객체 생성을 전담하는 '공장'을 세우기로 했습니다.

1. 팩토리 메서드 패턴: 무엇을 만들지 결정하지 마라

팩토리 메서드 패턴의 핵심은 **“객체 생성을 서브클래스에 위임하는 것”**입니다. 상위 클래스는 어떤 객체가 만들어질지 정확히 모른 채, 단지 "무언가 만들어져야 한다"는 추상적인 명령만 내립니다. 구체적인 탄생의 신비는 하위 클래스가 담당하게 되죠.

이 구조를 통해 우리는 '확장에는 열려 있고 수정에는 닫혀 있어야 한다'는 객체지향의 원칙인, **개방-폐쇄 원칙(OCP)**을 달성하게 됩니다.

image

2. C++로 구현하는 유닛 생산 공장

게임 속 캐릭터 생산 시스템을 예로 들어보겠습니다. 전사가 나올지, 마법사가 나올지 메인 로직은 알 필요가 없습니다. 그저 '유닛 공장'에게 요청할 뿐이죠.

image

#include <iostream>
#include <string>
#include <memory>

using namespace std;

// 1. 추상 제품 (Abstract Product): 모든 유닛의 공통 조상
class Unit {
public:
    virtual ~Unit() {}
    virtual void attack() = 0;
};

// 2. 구체적인 제품 (Concrete Products): 실제 탄생할 객체들
class Warrior : public Unit {
public:
    void attack() override { cout << ">> [전사] 검을 휘두릅니다!" << endl; }
};

class Mage : public Unit {
public:
    void attack() override { cout << ">> [마법사] 파이어볼을 발사합니다!" << endl; }
};

// 3. 추상 생산자 (Creator): 공장의 설계도
class UnitFactory {
public:
    virtual ~UnitFactory() {}
    
    // 이것이 바로 '팩토리 메서드'입니다.
    virtual unique_ptr<Unit> createUnit() = 0;

    // 공통 비즈니스 로직: 유닛을 만들고 기본 설정을 수행합니다.
    void produceAndDeploy() {
        unique_ptr<Unit> unit = createUnit();
        cout << "[공장] 유닛 생산 완료. 전선으로 배치합니다." << endl;
        unit->attack();
    }
};

// 4. 구체적 생산자 (Concrete Creators): 실제 유닛을 찍어내는 전용 공장
class WarriorFactory : public UnitFactory {
public:
    unique_ptr<Unit> createUnit() override {
        return make_unique<Warrior>();
    }
};

class MageFactory : public UnitFactory {
public:
    unique_ptr<Unit> createUnit() override {
        return make_unique<Mage>();
    }
};

3. 분석: 'new'라는 운명으로부터의 해방

위 코드에서 가장 인상적인 부분은 무엇일까요? 바로 UnitFactory는 자기가 무엇을 만들지 구체적으로 명시하지 않는다는 점입니다.

  • 결합도의 파괴: 사용자는 Warrior나 Mage라는 구체적인 클래스 이름을 몰라도 됩니다. 오직 Unit이라는 인터페이스와 UnitFactory라는 통로만 알면 되죠.
  • 확장의 용이성: 새로운 유닛인 '궁수(Archer)'가 추가된다고 가정해 보세요. 우리는 기존의 전사 공장이나 마법사 공장을 건드릴 필요가 없습니다. 그저 Archer 클래스와 ArcherFactory를 새롭게 정의하기만 하면 됩니다. 기존 코드를 수정하지 않고도 시스템이 확장되는 이 경이로움, 그것이 팩토리 패턴의 본질입니다.

하지만 모든 기술이 그렇듯 그림자도 존재합니다. 제품이 하나 추가될 때마다 공장 클래스도 함께 만들어야 하기에, 클래스의 숫자가 기하급수적으로 늘어나는 '클래스 인플레이션'을 겪을 수 있습니다. 간단한 객체 하나를 만드는데 굳이 거대한 공장을 세우는 것이 과연 합리적인지는 늘 고민해봐야 할 지점이죠.

4. 확장: 클라우드 시대의 동적 팩토리

미래의 시스템은 더욱 유동적일 것입니다. 우주 공간의 데이터센터에서 자원이 부족해지면, 시스템은 스스로 어떤 최적화 객체를 생성할지 판단해야 할지도 모릅니다. 이때 팩토리 패턴은 '런타임 설정'에 따라 객체 생성을 동적으로 전환하는 강력한 도구가 됩니다.설정 파일 하나만 바꾸면 시스템 전체의 객체 유형이 바뀌는 모습, 그것은 마치 연금술사가 납을 금으로 바꾸는 것과 같은 마법처럼 보일 수 있어요. 하지만 그 마법의 이면에는 철저하게 계산된 '추상화'라는 과학이 숨어 있습니다.

창조의 책임을 나누는 지혜

팩토리 패턴은 우리에게 "모든 것을 직접 하려 하지 마라"고 충고합니다. 객체의 탄생이라는 무거운 책임을 전담 공장에 위임함으로써, 우리는 비로소 객체의 '활용'이라는 더 중요한 가치에 집중할 수 있게 됩니다.

여러분의 프로젝트는 어떤가요? 혹시 코드 곳곳에 new라는 이름의 강한 의존성이 독버섯처럼 퍼져 있지는 않나요? 만약 변화가 두렵다면, 이제 그 창조의 권한을 우아한 팩토리에게 넘겨줄 때가 된 것일지도 모릅니다. 결국 훌륭한 설계란 무엇을 만드느냐보다, 어떻게 변화를 수용하느냐의 문제니까요. 여러분은 오늘 어떤 공장을 세우고 싶으신가요?

공유하기