Factory Pattern
객체 생성 책임을 클라이언트에서 팩토리로 옮겨 결합도를 낮추는 패턴. Simple Factory, Factory Method, Abstract Factory 세 형태를 비교.
난이도 입문 · 선행 없음
한 줄 요약
“무엇을 만들지” 결정하는 책임을 클라이언트에서 떼어내 별도의 객체(또는 메서드)로 옮긴다. 그래서 클라이언트는 구체 클래스를 모른 채 추상 인터페이스로만 일한다.
정확히 말하면 한 패턴이 아니다. 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();
}
- 분기는 여전히 있지만 한 곳에만 있다.
- 클라이언트는
Shape와ShapeFactory만 알면 된다.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 Method | Abstract 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(...)가 짧고 읽기 쉽다.