※ Simple Factory는 디자인 패턴이라기보단 프로그래밍을 하는데 있어서 자주 쓰이는 관용구에 가깝다.
객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하게 만듬. 즉 팩토리 메서드 패턴을 이용하면 클래스의 인스턴스를 만드는 일을 서브클래스(NYStylePizzaStore or ChicagoStylePizzaStore)에 위임.
(장점)
(단점)
여러 장점이 있지만 우선 객체 생성 코드를 전부 한 객체(Simple Factory) 또는 메소드(Factory Method)에 집어넣으면 코드에서 중복되는 내용을 제거할 수 있고, 나중에 관리할 때도 한 군데에만 신경을 쓰면 된다. 그리고 클라이언트 입장에서는 객체 인스턴스를 만들 때 필요한 구체 클래스가 아닌 인터페이스만 필요로 하게됨. 이러한 방법을 도입하면 구현이 아닌 인터페이스를 바탕으로 프로그래밍을 할 수 있게 되고, 그 결과 유연성과 확장성이 뛰어난 코드를 만들 수 있게 된다.
먼저 세 가지 서로 다른 팩토리를 만든 다음 사용하는 방식이 있을 것이다.
NYPizzaFactory nyFactory = new NYPizzaFactory();
PizzaStore nyStore = new PizzaStore(nyFactory);
nyStore.order("Veggie");
ChicagoPizzaFactory chicagoFactory = new ChicagoPizzaFactory();
PizzaStore chicagoStore = new PizzaStore(chicagoFactory);
chicagoStore.order("Veggie");
위와 같은 방식은 분점에서 개발자가 작성한 팩토리를 써서 피자를 만들긴 하는데, 독자적인 방법들을 사용할 수가 없다. 굽는 방식이 달라진다거나 종종 피자를 자르는 것을 까먹어 버리는 일이 일어나기 시작한 것이다. 이는 프렌차이즈마다 맛과 모양이 달라질 수 있다는 문제를 나을 수 있다.
따라서 피자 가게와 피자 제작 과정 전체를 하나로 묶어주는 프레임워크를 만들어야 되겠다는 결론에 도달함.
정리하자면, 피자 사업이 확장되어 여러 지역별로 각각의 다른 스타일의 피자를 만들어 내야하는 문제 발생.
=> 피자 가게와 피자 제작 과정 전체를 하나로 묶어주는 프레임워크를 만들어야 한다. 피자를 만드는 활동 자체는 전부 PizzaStore 클래스에 국한시키면서도 분점마다 고유의 스타일을 살릴 수 있는 방법을 택한다.
피자를 만드는 활동 자체는 전부 PizzaStore 클래스에 국한시키면서도 분점마다 고유의 스타일을 살릴 수 있도록 하는 방법이 있다. createPizza() 메서드를 PizzaStore()에 다시 집어넣는다. 하지만 추상 메서드로 선언하고, 각 지역마다 고유의 스타일에 맞게 PizzaStore의 서브클래스를 만들도록 한다.
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza = null;
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
public abstract Pizza createPizza(String type);
}
비교)
Pizza orderPizza(String type) { Pizza pizza; /////////// // // 피자 객체 만들기 if(type.equals("cheese")) pizza = new CheesePizza(); else if(type.equals("greek")) pizza = new GreekPizza(); else if(type.equals("pepperoni")) pizza = new PepperoniPizza(); /////////// // // 작업 pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; }
각 분점을 위한 서브클래스가 필요하다. 이제 각 지역별로 서브클래스를 만들어야 한다. 피자의 스타일은 각 서브클래스에서 결정한다.
public class NYPizzaStore extends PizzaStore{
@Override
public Pizza createPizza(String type){
Pizza pizza = null;
if(type.equals("cheese"))
pizza = new NYStyleCheesePizza();
else if(type.equals("peper"))
pizza = new NYStylePepperoniPizza();
else if(type.equals("clam"))
pizza = new NYStyleClamPizza();
else if(type.equals("veggie"))
pizza = new NYStyleVeggiePizza();
return pizza;
}
}
public class ChicagoPizzaStore extends PizzaStore{
@Override
public Pizza createPizza(String type){
Pizza pizza = null;
if(type.equals("cheese"))
pizza = new ChicagoStyleCheesePizza();
else if(type.equals("peper"))
pizza = new ChicagoStylePepperoniPizza();
else if(type.equals("clam"))
pizza = new ChicagoStyleClamPizza();
else if(type.equals("veggie"))
pizza = new ChicagoStyleVeggiePizza();
return pizza;
}
}
Pizza 구체 클래스
// 뉴욕 스타일 치즈 피자
public class NYStyleCheesePizza extends Pizza{
public NYStyleCheesePizza() {
this.name = "NY Style CheesePizza";
this.dough = "Thin Crust Dough";
this.sauce = "Marinara Sauce";
this.toppings.add("Grated Reggiano Cheese");
}
}
// 시카고 스타일 치즈 피자
public class ChicagoStyleCheesePizza extends Pizza{
public ChicagoStyleCheesePizza() {
this.name = "Chicago Style CheesePizza";
this.dough = "Extra Thick Crust Dough";
this.sauce = "Plum Tomato Sauce";
this.toppings.add("Shredded mozzarella Cheese");
}
@Override
public void cut() {
System.out.println("Cutting the pizza into square slices");
}
}
Main
public class Main {
public static void main(String[] args) {
PizzaStore pizzaStore = new NYStylePizzaStore();
Pizza pizza = pizzaStore.orderPizza("cheese"); // 뉴욕 스타일 치즈피자 객체 반환
System.out.println("We ordered a " + pizza.getName() + '\n');
System.out.println(pizza);
}
}
PizzaStore의 orderPizza() 메서드에 이미 주문 시스템(orderPizza())이 잘 갖춰져 있다. 이 주문 시스템 자체는 모든 분점에서 똑같이 진행되어야 한다.
각 분점마다 달라질 수 있는 것은 피자 스타일 뿐이다. 이렇게 달라지는 점들은 createPizza() 메서드에 집어넣고 그 메서드에서 해당 스타일의 피자를 만드는 것을 모두 책임지도록 할 계획인 것이다. PizzaStore의 서브클래스에서 createPizza() 메서드를 구현하도록 하면 된다.
PizzaStore 프레임워크에도 충실하면서도 각각의 스타일을 제대로 구현할 수 있는 orderPizza() 메서드를 가지고 있는 PizzaStore 서브클래스들을 구비할 수 있다.
(다른 표현) PizzaStore 추상 클래스에는 orderPizza() 메서드와 createPizza() 메서드(추상, 팩토리 메서드)가 있다. orderPizza 메서드에서는 팩토리 메서드에 의해 생산된 제품을 가지고 필요한 작업을 처리한다. 하지만 실제 팩토리 메서드를 구현하고 제품을 만들어내는 일은 서브클래스(NYStylePizzaStore 또는 ChicagoStylePizzaStore)에서만 할 수 있다.
=> "클래스의 인스턴스를 만드는 일을 서브클래스에 맡긴다."
PizzaStore의 orderPizza() 메서드는 추상 클래스(PizzaStore)에서 정의되어 있다. 그리고 이 메서드 안에서는 Pizza 객체를 가지고 여러 가지 작업(피자 준비, 굽기, 자르기, 포장하기 등)을 하긴 하지만, Pizza는 추상 클래스이기에 orderPizza()에서는 실제로 어떤 구체 클래스(concreate class)에서 작업이 처리되고 있는지 전혀 알 수 없다. 바꾸말하자면, PizzaStore와 Pizza는 서로 완전히 분리되어 있다.
PizzaStore nyPizzaStore = new NYPizzaStore();
nyPizzaStore.orderPizza("cheese");
NYPizzaStore을 선택했다면, orderPizza() 입장에서는 그 서브클래스에서 피자 종류를 결정한다고 할 수 있을 것이다. 만들어지는 피자의 종류를 해당 서브클래스에서 결정한다는 말이다.
구체 클래스(NYPizzaStore, ChicagoPizzaStore)의 인스턴스를 만드는 일을 한 객체에서 전부 처리하는 방식(Simple Factory)에서 일련의 서브클래스에서 처리하는 방식(Factory method)으로 넘어오게 되었다.
// PizzaStore 클래스
// 추상 클래스 내 팩토리 메서드 선언
protected abstract Pizza createPizza(String type);
팩토리 메서드는 객체 생성을 처리하며, 팩토리 메서드를 이용하면 객체를 생성하는 작업을 서브클래스에서 캡슐화시킬 수 있다. 이렇게 하면 수퍼클래스에 있는 클라이언트 코드(orderPizza())와 서브클래스에 있는 객체 생성 코드(createPizza())를 분리시킬 수 있다.
cf) 의존성 뒤짚기 원칙(Dependency Inversion Principle, DIP)
추상화된 것에 의존하도록 만들어라. 구상 클래스에 의존하도록 만들지 않도록 한다.
PizzaStore -> NYStyleCheesePizza
PizzaStore -> ChicagoStyleCheesePizza
이런식으로 의존되었던 디자인이.
PizzaStore -> Pizza
Pizza <- NYStyleCheesePizza
Pizza <- ChicagoStyleCheesePizza
이런식으로 의존관계가 뒤짚어졌다.