객체를 무엇으로 만들지의 결정을 미루는 패턴들.
팩토리 메서드 — 생성을 하위 클래스로
abstract class Dialog {
abstract Button createButton(); // 팩토리 메서드
void render() {
Button b = createButton(); // 무엇이 만들어질지 여기서는 모른다
b.onClick(this::close);
}
}
class WindowsDialog extends Dialog {
Button createButton() { return new WindowsButton(); }
}
"뼈대는 상위가 정하고 부품은 하위가 정한다" — Template Method와 짝을 이룬다.
추상 팩토리 — 제품 '군'을 통째로
interface UIFactory {
Button createButton();
Checkbox createCheckbox(); // 여러 종류를 한 팩토리가 만든다
}
class MacFactory implements UIFactory {
public Button createButton() { return new MacButton(); }
public Checkbox createCheckbox() { return new MacCheckbox(); }
}
목적: 함께 쓰여야 하는 것들이 섞이지 않게 한다
- MacButton 과 WindowsCheckbox 가 섞이는 일을 구조적으로 막는다
둘의 차이
| 팩토리 메서드 | 제품 '하나'. | 상속으로 확장한다 |
|---|---|---|
| 추상 팩토리 | 제품 '군'. | 합성(팩토리 객체를 주입)으로 쓴다 |
- 추상 팩토리는 내부적으로 팩토리 메서드를 여러 개 갖는 형태가 흔하다
정적 팩토리 메서드는 다른 것이다
// GoF 패턴이 아니라 '관용구' 다
Integer.valueOf(1)
List.of(1, 2, 3)
Optional.ofNullable(x)
장점
- 이름을 줄 수 있다 (생성자는 클래스명으로 고정)
- 매번 새 객체를 안 만들어도 된다 (캐싱)
- 하위 타입을 반환할 수 있다
면접에서 "팩토리 패턴"이라고 하면 셋 중 무엇인지 되묻는 게 안전하다.
언제 과한가
구현이 하나뿐이고 늘어날 계획도 없다면 그냥 new 가 낫다
- 팩토리는 '무엇을 만들지가 런타임에 갈릴 때' 값을 한다
면접 함정
- ❌ "팩토리 메서드는 static이다" → GoF의 팩토리 메서드는 상속으로 재정의하는 인스턴스 메서드다.
- ❌ "추상 팩토리가 더 발전된 형태" → 목적이 다르다. 제품이 하나면 팩토리 메서드가 맞다.
실무에서 가장 흔한 형태
// 타입 → 구현 매핑을 한곳에 모은다
@Component
class PayMethodFactory {
private final Map<PayType, PayMethod> methods;
PayMethodFactory(List<PayMethod> list) { // DI 가 구현을 전부 주입한다
this.methods = list.stream().collect(toMap(PayMethod::type, identity()));
}
PayMethod get(PayType type) {
return Optional.ofNullable(methods.get(type))
.orElseThrow(() -> new IllegalArgumentException("지원하지 않는 결제: " + type));
}
}
새 결제 수단을 추가할 때 이 팩토리를 안 고쳐도 된다 — 구현체를 빈으로 등록하기만 하면 목록에 자동으로 들어온다. OCP가 프레임워크 수준에서 완성된 형태다.
이름 짓기
-
of · from · valueOf — 변환·생성 (List.of · Integer.valueOf)
-
getInstance — 캐싱된 인스턴스
-
newInstance — 항상 새 인스턴스
-
create — 일반적인 생성
-
이름만으로 "매번 새로 만드는가" 를 알 수 있게 하는 것이 관례다