포스트

Factory Pattern

객체 생성 책임을 클라이언트에서 팩토리로 옮겨 결합도를 낮추는 패턴. Simple Factory, Factory Method, Abstract Factory 세 형태를 비교.

Factory Pattern

난이도 입문 · 선행 없음

한 줄 요약

“무엇을 만들지” 결정하는 책임을 클라이언트에서 떼어내 별도의 객체(또는 메서드)로 옮긴다. 그래서 클라이언트는 구체 클래스를 모른 채 추상 인터페이스로만 일한다.

정확히 말하면 한 패턴이 아니다. Simple Factory(관용 표현), Factory Method(GoF), Abstract Factory(GoF) 세 형태가 묶여 “팩토리”라 불린다. 결합도를 낮추는 방향성은 같다.

어떤 문제를 푸는가

도형을 그리는 코드. 클라이언트가 직접 new를 호출한다.

1
2
3
4
5
6
7
8
void draw(const std::string& type) {
    Shape* shape;
    if (type == "circle")       shape = new Circle();
    else if (type == "square")  shape = new Square();
    else if (type == "triangle") shape = new Triangle();
    shape->draw();
    delete shape;
}

문제:

  • 클라이언트가 모든 구체 클래스(Circle, Square, …)에 의존한다. 새 도형이 늘면 클라이언트가 수정된다.
  • 같은 분기 코드가 여러 호출 사이트에 흩어진다.
  • 도형이 추상 인터페이스를 갖고 있어도, 생성 시점에 추상화가 깨진다.

1. Simple Factory

가장 가벼운 형태. 생성 분기를 한 곳(팩토리 클래스)으로 모은다. GoF에 없는 관용 패턴이지만 가장 자주 보인다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <memory>
#include <string>

class Shape {
public:
    virtual void draw() = 0;
    virtual ~Shape() = default;
};

class Circle : public Shape   { void draw() override { std::cout << "○\n"; } };
class Square : public Shape   { void draw() override { std::cout << "□\n"; } };
class Triangle : public Shape { void draw() override { std::cout << "△\n"; } };

class ShapeFactory {
public:
    static std::unique_ptr<Shape> create(const std::string& type) {
        if (type == "circle")   return std::make_unique<Circle>();
        if (type == "square")   return std::make_unique<Square>();
        if (type == "triangle") return std::make_unique<Triangle>();
        return nullptr;
    }
};

int main() {
    auto shape = ShapeFactory::create("circle");
    shape->draw();
}
  • 분기는 여전히 있지만 한 곳에만 있다.
  • 클라이언트는 ShapeShapeFactory만 알면 된다. Circle/Square는 모른다.
  • 단점: 새 도형 추가 시 팩토리를 수정해야 한다 (OCP 미충족).

2. Factory Method (GoF)

생성을 서브클래스에게 위임한다. 분기 대신 다형성을 쓴다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Shape/Circle/Square는 Simple Factory 섹션과 동일 — 재정의는 자체 검증 편의용
class Shape { public: virtual void draw() = 0; virtual ~Shape() = default; };
class Circle : public Shape { void draw() override { std::cout << "○\n"; } };
class Square : public Shape { void draw() override { std::cout << "□\n"; } };

class Creator {
public:
    void render() {
        auto shape = createShape();  // 어떤 도형인지 모름
        shape->draw();
    }
    virtual std::unique_ptr<Shape> createShape() = 0;
    virtual ~Creator() = default;
};

class CircleCreator : public Creator {
    std::unique_ptr<Shape> createShape() override {
        return std::make_unique<Circle>();
    }
};

class SquareCreator : public Creator {
    std::unique_ptr<Shape> createShape() override {
        return std::make_unique<Square>();
    }
};

int main() {
    std::unique_ptr<Creator> creator = std::make_unique<CircleCreator>();
    creator->render();
}
  • Creator::render()는 구체 도형을 모른다. createShape()만 호출.
  • 새 도형 추가 = 새 Creator 서브클래스. 기존 Creator 무손상 (OCP 충족).
  • 단점: 도형 종류마다 Creator 클래스가 필요해 객체가 두 배로 늘어난다.

Factory Method는 Template Method 패턴의 특수한 형태다. render()가 흐름을 정의하고, createShape()이 하위가 채우는 단계.

3. Abstract Factory (GoF)

서로 관련된 객체들의 묶음을 생성한다. 묶음 자체를 교체할 때 쓴다.

대표 예: GUI 툴킷. Windows 룩이면 Windows 스타일 버튼·창·체크박스를 함께, Mac 룩이면 Mac 스타일을 함께.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Button { public: virtual void render() = 0; virtual ~Button() = default; };
class Window { public: virtual void render() = 0; virtual ~Window() = default; };

class WindowsButton : public Button { void render() override { std::cout << "Windows 버튼\n"; } };
class WindowsWindow : public Window { void render() override { std::cout << "Windows 창\n"; } };
class MacButton     : public Button { void render() override { std::cout << "Mac 버튼\n"; } };
class MacWindow     : public Window { void render() override { std::cout << "Mac 창\n"; } };

class UIFactory {
public:
    virtual std::unique_ptr<Button> createButton() = 0;
    virtual std::unique_ptr<Window> createWindow() = 0;
    virtual ~UIFactory() = default;
};

class WindowsUIFactory : public UIFactory {
    std::unique_ptr<Button> createButton() override { return std::make_unique<WindowsButton>(); }
    std::unique_ptr<Window> createWindow() override { return std::make_unique<WindowsWindow>(); }
};

class MacUIFactory : public UIFactory {
    std::unique_ptr<Button> createButton() override { return std::make_unique<MacButton>(); }
    std::unique_ptr<Window> createWindow() override { return std::make_unique<MacWindow>(); }
};

void buildApp(UIFactory& factory) {
    auto window = factory.createWindow();
    auto button = factory.createButton();
    window->render();
    button->render();
}

int main() {
    WindowsUIFactory winFactory;
    buildApp(winFactory);

    MacUIFactory macFactory;
    buildApp(macFactory);
}
  • 룩앤필 전체를 한 번에 교체한다. Windows와 Mac을 섞을 일이 없도록 강제된다.
  • 단점: 묶음에 새 제품군(예: Menu)을 추가하려면 UIFactory와 모든 구현체를 수정해야 한다 (OCP 깨짐).

Factory Method vs Abstract Factory

 Factory MethodAbstract Factory
생성 대상하나의 제품관련 제품 묶음
메커니즘상속 (서브클래스가 생성)위임 (팩토리 객체가 생성)
새 제품 추가 시새 Creator 서브클래스 1개모든 ConcreteFactory에 새 메서드 추가
새 묶음 추가 시해당 없음새 ConcreteFactory 1개
변하는 축1차원 (제품 종류)2차원 (제품 종류 × 묶음)

두 패턴은 자주 함께 쓰인다. Abstract Factory의 각 메서드를 Factory Method로 구현하기도.

어느 걸 언제 쓰나

  • 도형이 3개고 안 늘 것 같다 → 그냥 new. 패턴 도입 비용이 더 크다.
  • 도형 종류가 계속 늘고 클라이언트가 한 곳뿐이다 → Simple Factory.
  • 도형이 계속 늘고 클라이언트(Creator)가 여러 종류다 → Factory Method.
  • “Windows 룩 / Mac 룩” 같은 묶음 단위 교체가 필요하다 → Abstract Factory.

실전 사례

  • std::make_unique / std::make_shared: 생성을 캡슐화한 함수 팩토리.
  • JDBC DriverManager.getConnection(url): URL에 따라 MySQL·Postgres 드라이버 객체 반환 (Simple Factory).
  • Spring BeanFactory / @Bean 메서드: 빈 생성을 컨테이너에 위임.
  • DOM document.createElement(tag): 태그명에 따라 적절한 요소 객체 반환.

안티패턴 / 주의

  • 타입 분기 enum + switch도 Simple Factory일 수 있다. 생성 분기를 한 곳에 모았다는 점이 핵심이다. 다만 switch가 계속 커지면 변경이 그 팩토리에 집중되고 OCP 한계가 드러난다. 실제 확장 요구가 생기면 Factory Method나 등록 기반 구조를 검토한다.
  • 모든 객체 생성을 팩토리로 감싸지 마라. 단순 DTO·값 객체는 그냥 생성자가 명확하다. 팩토리는 (1) 분기가 있거나 (2) 생성 자체에 의미 있는 이름이 필요할 때 가치가 있다.
  • Abstract Factory의 제품군은 정말 묶여 있어야 한다. WindowsButton + MacWindow 같은 조합이 의미 있다면 그 묶음은 잘못 잡은 것.
  • 팩토리에 비즈니스 로직을 넣지 마라. 팩토리는 “어떻게 만들지”만 안다. “언제 만들지”·”왜 만들지”는 호출자의 책임.

스스로 점검

1. 도형 종류가 계속 늘어난다. Simple Factory와 Factory Method 중 OCP를 만족하는 쪽은?

Factory Method. 새 도형 = 새 Creator 서브클래스 추가, 기존 코드 무손상. Simple Factory는 내부의 if/else 분기를 수정해야 하므로 OCP를 깬다.

2. Abstract Factory를 적용했는데 “WindowsButton + MacWindow” 조합이 의미 있다면?

묶음 기준이 잘못 잡힌 신호. Abstract Factory의 제품군은 반드시 묶여 있어야 한다. 섞이는 조합이 의미 있다면 처음부터 Strategy 같은 다른 패턴이 맞을 수 있다.

3. 단순 DTO (User(name, age))에 팩토리를 만드는 게 과한 이유는?

팩토리는 “무엇을 만들지” 결정·이름 부여가 필요할 때 가치 있다. 분기도 없고 생성자가 명확한 DTO는 그냥 new User(...)가 짧고 읽기 쉽다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.