서로 관련된 객체들을 군(family) 단위로 통째로 생성하는 패턴.
무엇을 푸는가
-
Windows 테마면 — WinButton + WinCheckbox + WinScrollBar
-
Mac 테마면 — MacButton + MacCheckbox + MacScrollBar
-
섞이면 안 된다 — WinButton 옆에 MacCheckbox 가 놓이면 안 된다
-
개별 생성에 맡기면 조합의 일관성을 보장할 수 없다
interface GuiFactory {
Button createButton();
Checkbox createCheckbox(); // 제품이 여러 종류
}
class WinFactory implements GuiFactory {
public Button createButton() { return new WinButton(); }
public Checkbox createCheckbox() { return new WinCheckbox(); }
}
// 클라이언트는 어느 팩토리인지 모른 채 쓴다 → 군의 일관성이 자동으로 지켜진다
void render(GuiFactory f) {
f.createButton().paint();
f.createCheckbox().paint();
}
Factory Method와의 차이
- Factory Method — 제품 '하나' 의 생성을 하위 클래스에 위임
- 상속으로 확장 (하위 클래스가 오버라이드)
Abstract Factory 제품 '군' 의 생성을 팩토리 객체에 위임
-
합성으로 확장 (팩토리를 주입)
-
실제로 Abstract Factory 의 각 메서드가 Factory Method 로 구현되는 경우가 많다
-
대립 관계가 아니라 층위가 다르다
대가 — 제품 추가가 어렵다
새 '테마'(Linux) 추가 → 팩토리 클래스 하나만 추가하면 된다 ✓ 쉽다 새 '제품'(Slider) 추가 → 인터페이스와 모든 구현 팩토리를 수정 ✗ 어렵다
제품 종류가 자주 바뀌는 도메인에는 맞지 않는다
실무에서
DocumentBuilderFactory · TransformerFactory // JDK XML API
ConnectionFactory // JMS — Connection·Session 군
면접 함정
- ❌ "팩토리 메서드의 상위 호환" → 해결하는 문제가 다르다. 하나 vs 군.
- ❌ 제품이 하나뿐인데 도입 → 그냥 Factory Method나 단순 팩토리로 충분하다.
팩토리를 어떻게 고르나
// 런타임 조건으로 한 번만 정하고, 이후는 인터페이스로만 다룬다
GuiFactory factory = switch (os) {
case WINDOWS -> new WinFactory();
case MAC -> new MacFactory();
};
new Application(factory).run(); // 이 아래로는 OS 를 모른다
선택 지점을 한 곳으로 모으는 것이 핵심이다. 곳곳에 if (os == WINDOWS) 가 흩어져 있으면 패턴을 쓴 의미가 없다.
스프링에서는 DI가 대신한다
@Bean @Profile("prod") GuiFactory prodFactory() { return new WinFactory(); }
@Bean @Profile("test") GuiFactory testFactory() { return new StubFactory(); }
프레임워크가 조립을 맡으면 팩토리 클래스를 직접 만들 일이 줄어든다. 다만 제품 군의 일관성을 지킨다는 발상은 그대로 쓰인다.
언제 과설계인가
- 제품 군이 하나뿐이다 — → 팩토리 계층이 순수한 비용
- 군이 늘어날 가능성이 없다 — → 단순 팩토리로 충분
- 제품 종류가 자주 바뀐다 — → 인터페이스를 계속 고쳐야 해 오히려 방해