팩토리 메서드 패턴
IT위키
더 많은 작업
팩토리 메서드 패턴(Factory Method Pattern)은 객체를 만드는 인터페이스는 상위 클래스가 정하고, 어떤 클래스의 객체를 만들지는 하위 클래스가 결정하도록 생성 책임을 위임하는 생성 패턴이다.
팩토리 메서드 패턴은 GoF 디자인 패턴의 생성(Creational) 패턴 가운데 하나로, 가상 생성자(Virtual Constructor) 패턴이라고도 한다. 클라이언트 코드 곳곳에 new 구체클래스() 가 흩어져 있으면, 새 제품 클래스가 추가될 때마다 그 코드를 모두 고쳐야 한다. 팩토리 메서드 패턴은 객체 생성을 한 메서드로 모으고 그 메서드를 하위 클래스가 재정의하게 해서, 클라이언트는 인터페이스(추상 타입)에만 의존하게 만든다.
| 참여자 | 역할 |
|---|---|
| Product | 팩토리 메서드가 만드는 객체의 공통 인터페이스(또는 추상 클래스) |
| ConcreteProduct | Product 를 구현한 실제 클래스 |
| Creator | 팩토리 메서드를 선언한다. Product 를 쓰는 공통 로직도 여기 둔다 |
| ConcreteCreator | 팩토리 메서드를 재정의해 특정 ConcreteProduct 를 만들어 돌려준다 |
Creator (abstract) Product (interface)
+ anOperation() ── uses ──▶ △
+ factoryMethod() : Product │ implements
△ │
│ extends ConcreteProduct
ConcreteCreator ▲
+ factoryMethod() ──── new ────────────┘
주문 메서드 안에서 피자 종류를 보고 직접 객체를 만든다. 새 피자가 생기면 orderPizza() 를 고쳐야 한다.
public class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;
if (type.equals("combination")) {
pizza = new CombinationPizza();
} else if (type.equals("cheese")) {
pizza = new CheesePizza();
} else {
pizza = new PepperoniPizza();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
// Factory 클래스
public class SimplePizzaFactory {
public Pizza createPizza(String type) {
if (type.equals("combination")) return new CombinationPizza();
if (type.equals("cheese")) return new CheesePizza();
if (type.equals("pepperoni")) return new PepperoniPizza();
throw new IllegalArgumentException("unknown pizza: " + type);
}
}
// Factory 클래스를 사용하는 코드
public class PizzaStore {
private final SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza = factory.createPizza(type); // new 가 사라졌다
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
// 사용
PizzaStore store = new PizzaStore(new SimplePizzaFactory());
Pizza p = store.orderPizza("cheese");
PizzaStore는 이제 구체 피자 클래스를 모른다. 피자 종류가 바뀌어도 팩토리만 고친다.- 단순 팩토리(Simple Factory)는 GoF 23개 패턴에 들지 않는 관용구이다. 조건 분기가 팩토리 안으로 옮겨 갔을 뿐 없어지지는 않았다.
지점마다 다른 피자를 만들어야 하면, 생성 메서드를 추상 메서드로 두고 하위 클래스가 구현한다.
// Product
public abstract class Pizza {
public void prepare() { System.out.println("prepare " + getClass().getSimpleName()); }
public void bake() { System.out.println("bake"); }
public void cut() { System.out.println("cut"); }
public void box() { System.out.println("box"); }
}
// ConcreteProduct
public class SeoulCheesePizza extends Pizza { }
public class BusanCheesePizza extends Pizza { }
// Creator
public abstract class PizzaStore {
public final Pizza orderPizza(String type) {
Pizza pizza = createPizza(type); // 팩토리 메서드 호출
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
protected abstract Pizza createPizza(String type); // 팩토리 메서드
}
// ConcreteCreator
public class SeoulPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
if (type.equals("cheese")) return new SeoulCheesePizza();
throw new IllegalArgumentException(type);
}
}
public class BusanPizzaStore extends PizzaStore {
@Override
protected Pizza createPizza(String type) {
if (type.equals("cheese")) return new BusanCheesePizza();
throw new IllegalArgumentException(type);
}
}
// 클라이언트
PizzaStore store = new BusanPizzaStore();
Pizza pizza = store.orderPizza("cheese"); // BusanCheesePizza 가 만들어진다
- 주문 절차(
orderPizza)는 상위 클래스에 고정되고, 어떤 피자를 만들지는 하위 클래스가 정한다. - 새 지점은
PizzaStore를 상속한 클래스를 하나 더하면 된다. 기존 코드는 그대로다. - 상위 클래스가 처리 흐름을 정하고 일부 단계를 하위 클래스에 맡긴다는 점에서 템플릿 메서드 패턴과 함께 쓰이는 경우가 많다.
| 구분 | 단순 팩토리 | 팩토리 메서드 | 추상 팩토리 |
|---|---|---|---|
| 생성 책임 | 별도 팩토리 클래스의 메서드 하나 | 하위 클래스가 재정의한 메서드 | 제품군 전체를 만드는 팩토리 객체 |
| 확장 방법 | 팩토리 메서드 안의 분기 수정 | 새 하위 클래스(ConcreteCreator) 추가 | 새 팩토리 구현 클래스 추가 |
| 핵심 수단 | 위임(분기) | 상속 | 객체 구성(composition) |
| 만드는 대상 | 제품 하나 | 제품 하나 | 서로 연관된 제품 여러 개(제품군) |
| GoF 패턴 여부 | 아니다(관용구) | 생성 패턴 | 생성 패턴 |
- 장점
- 클라이언트가 구체 클래스에 의존하지 않는다
- 새 제품을 추가할 때 기존 코드를 고치지 않는다
- 생성 로직이 한곳에 모여 관리가 쉽다
- 단점
- 제품마다 Creator 하위 클래스가 늘어나 클래스 수가 많아진다
- 단순한 생성까지 패턴을 적용하면 구조만 복잡해진다