[헤드퍼스트 디자인패턴] 05. 팩토리 패턴(Factory Pattern)
1. 두가지 팩토리 패턴
추상 팩토리 패턴
- 구상 클래스에 의존하지 않고도 서로 연관되거나 의존적인 객체로 이루어진 제품군을 생성하는 인터페이스를 제공한다. 구상 클래스는 서브클래스에서 만든다.
팩토리 메소드 패턴
- 객체를 생성할 때 필요한 인터페이스를 만든다. 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정한다. 팩토리 메소드를 사용하면 인스턴스 만드는 일을 서브 클래스에 맡길 수 있다.
2. 요구사항
- 피자 가게에서 여러 피자를 판매 한다.
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;
}
위와 같이 코드를 작성한다면, 피자가 추가되거나 제거 될 때 코드가 변경 되어야 한다.
-> 바뀌는 부분과 바뀌지 않는 부분을 분리해 바뀌는 부분을 캡슐화 해야 한다.
객체 생성을 처리하는 부분을 빼내어 새로 만들고, 이것은 팩토리(Factory)라고 부르자.
public class SimplePizzaFactory {
public Pizza createPizza(String type){
Pizza pizza = null;
if(type.equals("cheese")) pizza = new CheesePizza();
else if(type.equals("pepper")) pizza = new PepperoniPizza();
else if(type.equals("clam")) pizza = new ClamPizza();
else if(type.equals("veggie")) pizza = new VeggiePizza();
return pizza;
}
}
public class PizzaStore{
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
위와 같이 설계하는 것은 "간단한 팩토리"로, 이것은 디자인 패턴이라기 보다는 프로그래밍에서 자주 쓰이는 관용구에 가깝다.
3. 요구 사항의 수정
- 피자 가게가 사업이 확장되어 여러 지역에 지점을 내고, 각 지점마다 그 지역의 특성과 입맛을 반영한 다양한 스타일의 피자를 만들어야 한다.
만약 앞에서 배운 간단한 팩토리를 사용한다면 어떻게 될까?
SimplePizzaFactory 를 빼고 세가지 서로 다른 팩토리 (NYPizzaFactory, ChicagoPizzaFactory, CalifornizPizzaFactory)를 만들어서 적용해보자.
PizzaStore nyStore = new PizzaStore(new NYPizzaFactory());
nyStore.orderPizza("veggie");
PizzaStore chicagoStore = new PizzaStore(new ChicagoPizzafactory());
chicagoStore.orderPizza("veggie");
지점에서 본사에서 만든 팩토리 패턴을 사용하긴 하는데, 굽는 방식이 달라진다거나 상자의 모양이 맞지 않는 일이 생긴다. 이 문제를 해결하려면 PizzaStore와 피자 제작 코드 전체를 하나로 묶어주면서, 유연성을 잃어 버리지 않는 프레임워크를 만들어야 한다.
4. 팩토리 메소드 패턴
피자를 만드는 일 전부를 PizzaStore 클래스에 진행하면서도 지점의 스타일을 살릴수 있는 방법이 있다. 이번에는 createPizza()를 다시 PizzaStore에 추상메소드로 선언하고, 지역별 스타일에 맞게 PizzaStore의 서브 클래스를 만든다.
public abstract class PizzaStore{
public Pizza orderPizza(String type){
Pizza pizza;
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
abstract Pizza createPizza(String type); // 팩토리 메소드가 PizzaStore의 추상 메소드로 바뀌었다.
}
이제 각 지점에 맞는 서브클래스를 만들어 보자. 피자의 스타일은 각 서브 클래스에서 결정 한다.
public class NYPizzaStore extends PizzaStore{
@Override
public Pizza createPizza(String item){
if(item.equals("cheese")) return new NYStyleCheesePizza();
if(item.equals("peperoni")) return new NYStylePepperoniPizza();
if(item.equals("clam")) return new NYStyleClamPizza();
if(item.equals("veggie")) return new NYStyleVeggiePizza();
return null;
}
}
public class ChicagoPizzaStore extends PizzaStore{
@Override
public Pizza createPizza(String item){
if(item.equals("cheese")) return new ChicagoStyleCheesePizza();
if(item.equals("peper")) return new ChicagoStylePepperoniPizza();
if(item.equals("clam")) return new ChicagoStyleClamPizza();
if(item.equals("veggie")) return new ChicagoStyleVeggiePizza();
return null;
}
}
피자를 실제로 만들어 보자.
public class PizzaTestDrive {
public static void main(String[] args) {
PizzaStore nyStore = new NYPizzaStore();
PizzaStore chicagoStore = new ChicagoPizzaStore();
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println(pizza.getname());
pizza = chicagoStore.orderPizza("cheese");
System.out.println(pizza.getname());
}
}
모든 팩토리 패턴은 객체 생성을 캡슐화 한다. 슈퍼클래스는 어떤 스타일의 피자가 만들어졌는지 전혀 알지 못한다. 다만 피자라는 건 알고 있기 때문에 그 피자를 준비하고, 굽고, 자르고, 포장하는 작업을 완료한다.
이렇게 함으로써, 기존에 모든 피자 객체를 PizzaStore 클래스 내에서 직접 만들었을 때의 심하게 의존적인 PizzaStore의 의존성을 줄일 수 있다.
디자인 원칙
추상화된 것에 의존하게 만들고
구상 클래스에 의존하지 않게 만든다.
5. 추상 팩토리 패턴
- 지역마다 같은 메뉴여도, 재료가 다를 수 있어 원재료를 생산하는 팩토리를 만들어 본다.
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClams();
}
모든 원재료를 생산하는 팩토리용 인터페이스를 정의한다.
public class NYPizzaingredientFactory implements PizzaIngredientFactory{
@Override
public Dough createDough() {
return new ThinCrustdough();
}
@Override
public Sauce createSauce() {
return new MarinaraSauce();
}
@Override
public Cheese createCheese() {
return new ReggianoCheese();
}
@Override
public Veggies[] createVeggies() {
Veggies veggies[] = { new Farlic(), new Onion(), new Mushroom(), new RedPepper() };
return veggies;
}
@Override
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
@Override
public Clams createClams() {
return new Freshclams();
}
}
각 지역에 맞는 원재료를 리턴하는 팩토리를 구현한다.
public abstract class Pizza{
String name;
Dough dough;
Sauce sauce;
Veggies veggies[];
Cheese cheese;
Pepperoni pepperoni;
Clams clams;
abstract void prepare(); //추상 메소드로 변경됨
//기타 메소드
}
public class CheesePizza extends Pizza{
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
@Override
public void prepare() {
this.dough = ingredientFactory.createDough();
this.sauce = ingredientFactory.createSauce();
this.cheese = ingredientFactory.createCheese();
}
}
Pizza의 prepare()를 추상 메소드화 하고, 구상 클래스에서 원재료를 팩토리에서 바로 가져와 prepare()를 구현하면, 피자마다 클래스를 지역별로 따로 만들 필요가 없다.
public class NYPizzaStore extends PizzaStore{
@Override
public Pizza createPizza(String type){
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaingredientFactory();
if(type.equals("cheese")){
pizza = new CheesePizza(ingredientFactory);
pizza.setName(ingredientFactory.NY_STYLE+" Cheese Pizza");
}else if(type.equals("peper")){
pizza = new PepperoniPizza(ingredientFactory);
pizza.setName(ingredientFactory.NY_STYLE+" Pepperoni Pizza");
}else if(type.equals("clam")){
pizza = new ClamPizza(ingredientFactory);
pizza.setName(ingredientFactory.NY_STYLE+" Clam Pizza");
}else if(type.equals("veggie")){
pizza = new VeggiePizza(ingredientFactory);
pizza.setName(ingredientFactory.NY_STYLE+" Veggie Pizza");
}
return pizza;
}
}
6. 정리
- 팩토리 패턴을 쓰면 객체 생성을 캡슐화할 수 있다.
- 팩토리 메소드 패턴에서는 상속을 활용한다. 객체 생성이 서브클래스에게 위임된다. 서브 클래스에서는 팩토리 메소드를 구현하여 객체를 생산한다.
- 추상 팩토리 패턴에서는 객체 구성을 활용한다. 객체 생성이 팩토리 인터페이스에서 선언한 메소스들에서 구현된다.
- 모든 팩토리 패턴에서는 애플리케이션의 구상 클래스에 대한 의존성을 줄여줌으로써 느슨한 결합을 도와준다.
- 추상 팩토리 패턴은 구상 클래스에 직접 의존하지 않고도 서로 관련된 객체들로 이루어진 제품군을 만들기 위한 용도로 쓰인다.
- DIP에 따르면 구상 형식에 대한 의존을 피하고 추상화를 지향할 수 있다.
- 팩토리는 구상 클래스가 아닌 추상 클래스, 인터페이스에 맞춰 코딩할 수 있게 해주는 강력한 기법이다.
'스터디 > 헤드퍼스트 디자인패턴' 카테고리의 다른 글
[헤드퍼스트 디자인패턴] 07. 어댑터 패턴(Adapter Pattern)과 퍼사드 패턴(Facade Pattern) (0) | 2022.07.17 |
---|---|
[헤드퍼스트 디자인패턴] 06. 커맨드 패턴(Command Pattern) (0) | 2022.07.10 |
[헤드퍼스트 디자인패턴] 04. 싱글턴 패턴(Singleton Pattern) (0) | 2022.06.18 |
[헤드퍼스트 디자인패턴] 03. 데코레이터 패턴(Decorator Pattern) (0) | 2022.06.18 |
[헤드퍼스트 디자인패턴] 02. 옵저버 패턴(Observer Pattern) (0) | 2022.06.12 |
댓글