reference: 수업 강의자료(헤드퍼스트 책)
When you have code that makes use of lots of concrete calsses, you're looking for trouble because that code may have to be changed as new concrete classes are added.
=> Violating OCP
생성자를 없애고 싶은 이유: 자바에서는 인터페이스를 이용하여 객체의 변화를 반영하는 좋은 방법이 있었지만, 이것 역시 생성자의 한계를 벗어날 수 없어 다른 코드의 변화로부터 독립적인 코드의 작성에 생성자가 걸림돌이 될 수 있다. 생성자는 각각 확정된 객체(Concrete object)가 있어야 호출될 수 있으며, 확정된 객체에 변화가 생기면 잠재적으로 수정될 가능성이 생긴다. 물론 여기에 있는 예제에서처럼 생성자가 쉽게 찾아 볼 수 있다면 편하겠지만, 수만, 수천 줄의 코드에 생성자 호출이 여기저기에 펼쳐져 있으면 그 수만, 수천 줄의 코드를 모두 살펴야 한다. 따라서 생성자를 따로 관리할 수 있다면 좀 더 변화에 대응하기가 쉽다.
=> 해결: Creational Pattern
이 패턴 중 하나가 Factory Pattern
Pizza orderPizza() {
Pizza pizza = new Pizza();
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
But we can't directly instantiate either or those..
만약 하나 이상의 타입의 피자가 있다면..?
public class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza = null;
// 추가 또는 수정될 수 있는 부분의 코드
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
//////////////////////////////////
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
만약 수정이 생기면..?
=> 변화하는 부분(이 예에선 if-else 문들)을 따로 한데 모으는 것만으로도 관리가 편해질 것이라 생각.
=> Simple Facotory(패턴까지는 아님)
// SimplePizzaFactory 클래스
// 수정이 빈번할 코드를 따로 빼 놓는다.
public class SimplePizzaFactory {
public Pizza createPizza(String type){
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
return pizza;
}
}
// 기존 PizzaStore 코드 변경
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza = null;
/* Simple Factory 적용후 삭제
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
*/
pizza = factory.createPizza("cheese");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
public class SimplePizzaFactory {
// static 메서드로 만들자!
public static Pizza createPizza(String type){
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("pepperoni")) {
pizza = new PepperoniPizza();
} else if (type.equals("clam")) {
pizza = new ClamPizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
return pizza;
}
}
Simple Factory의 장점은 이를 사용하는 클라이언트가 매우 많을 때 진가를 발휘한다. 위 코드에선 orderPizza() 메서드만 나와 있지만, 피자 객체를 받아서(객체 생성을 담당하는 것이 Simple Factory) 가격이라든가 피자에 대한 설명 등을 찾아서 활용하는 PizzaShopMenu 같은 클래스든가, PizzaStore에서 했던 것과 조금 다른 방식으로 피자 주문을 처리하는 HomeDelivery와 같은 클래스에서도 이 팩토리를 사용할 수 있을 것이다.
따라서 피자를 생성하는 작업(객체 생성)을 한 클래스(Simple Factory)에 캡슐화 시켜 놓으면 구현을 변경해야 하는 경우에 여기저기 다 들어가서 고칠 필요 없이 이 팩토리 클래스 하나만 고치면 된다!