문제 제기
- "new"는 "구상객체" 를 뜻한다. 즉 new를 사용해서 인스턴스를 만든다는 것은, 특정 구현된 클래스의 인스턴스를 만든다는 것이다. new를 바탕으로 코딩을 하면 나중에 코드를 수정해야할 가능성이 높아진다. 예를들어보자
Beverage beverage = new DarkRoast()
- 전에 데코레이트 패턴에서 카페시나리오에서 만들었던, 음료에 관한 코드이다. 만약 카페인이 싫은 사람은 무조건 디카페인을 먹는다면?, DarkRoast의 진한맛을 싫어하는 사람이 있다면?
Beverage beverage;
if(isNoCaf) {
beverage = new Decaf();
} else if(isStrong) {
beverage = new DarkRoast();
} else {
beverage = new HouseBlend();
}
- 이런식으로 조건에따라. 몇 가지의 구상 클래스의 인스턴스가 만들어지는 코드를 써야할것이다.
위와 같은 코드는 변경, 또는 확장이 필요할때 코드를 다시 지우거나 추가해야한다는 뜻이다. 휴먼오류를 일으킬 가능성과 OCP의 원칙을 지키기 어렵다.
- 사실 "new" 자체에는 문제가 없다. 그러나 변화를 원하는 고객과 요구사항이 항상 문제인것이다..... 그러면 우리는 어떻게 이러한 "new" 키워드의 OCP 문제를 해결할 수 있을까?
- 자, 우리가 피자가게를 차리는 시나리오를 생각해보자.
public Pizza orderPizza(String type) {
Pizza pizza;
if(type.equals("cheese")) {
pizza = new CheesePizz();
} else if(type.equlas("pepperoni") {
pizza = new PepperoniPizz();
} else if(type.equlas("boolgogi") {
pizza = new BoolgogiPizz();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
- 일단은 우리가 아까 고민했던, 위와같은 코드를 사용하여 보자.
치즈피자에 대한 단가가 안맞아, 치즈피자는 제외하고 야채피자(Veggie)를 새로 만든다고 해보자.
public Pizza orderPizza(String type) {
Pizza pizza;
//분기문에 피자 타입 코드변경에 관한 부분은 닫혀있지않다.
//즉 메뉴를 변경하려면 직접 코드를 수정해야한다.
if(type.equals("cheese")) {
pizza = new CheesePizz();
// } else if(type.equals("pepperoni") {
// pizza = new PepperoniPizza();
} else if(type.equals("boolgogi") {
pizza = new BoolgogiPizza();
} else if(type.equals("veggi") {
pizza = new VeggiPizza();
}
//이부분은 바뀌지 않는다.
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
- 위와같이 변경되지 않는부분과, 변경되는 부분이 명확하게 나뉘었다. 지금 orderPizza 메소드에서 가장 문제가 되는부분은, 인스턴스를 만들 피자종류 클래스를 선택하는 부분인것이다(OCP에 위배).
- 객체지향 디자인원칙에 따라, 바뀌는 부분에 대하여 캡슐화를 진행해보자,
팩토리 패턴에 단계적 적용
1)Simple Pizza Facotory
- 진짜 팩토리 패턴에 들어가기전에 워밍업으로 Simple Factory 을 만들어봅시다
public class SimplePizzaFactory {
public Pizza createPizaa(String type) {
Pizza pizza= null;
if(type.equals("cheese")) {
pizza = new CheesePizz();
} else if(type.equals("boolgogi")) {
pizza = new BoolgogiPizza();
} else if(type.equals("veggi")) {
pizza = new VeggiPizza();
}
return pizza;
}
}
public class PizzaStore {
SimplePizzaFactory factory;
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
- 위와같은 형태의 SimpleFactory는 널리 쓰이기는하나, 진짜 팩토리패턴이라고 말할수 없다. 왜냐하면, 실행시에 동적으로 SimplePizzaFactory에 관한 객체구성을 바꿀 수 없기때문이다. 이는 위 코드를 "팩토리 패턴" 이라고 부르는 사람이 있으면, 귓속말로 그건 팩토리 패턴이 아니라고 알려주자.
2)Factory Method Pattern
- 위의 심플피자 팩토리가 문제가 되는 경우에 대해서 이야기해보자. 우리의 피자가게가 번성하여, 우리의 피자가게가 해외로 진출을 하게되었다. 근데 인도에서는 피자를 Cutting하지 않고 손으로 찢어먹는다고한다고 가정해보자. 이 경우에 PizzaStore와 SimpleFactory 객체와의 강력한 결합떄문에 이를 해결 할 수 없다. (근데 만약 팩토리 자체를 인터페이스로 다중화시킨다면??)
-결론적으로 우리의 사업의 확장으로 Pizza Instance를 만들때 한 객체에서 만들기 보다는, 팩토리 메소드를 추상화하여 일련의 서브클래스에서 처리하는 방식으로 바꿔보자
public abstract class PizzaStore {
public Pizza orderPizza(String type) {
Pizza pizza;
//바뀐부분
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
//
protected abstract Pizza createPizza(String type);
}
- 위의 추상 PizzaStore 클래스로 여러가지 로컬 피자스토어 클래스를 만들 수 있게 되었다.
-이제 공장에서 Creator는 완성 되었으니, Product(Pizza)에 대해서 이야기 해보자,
public abstract class Pizza {
String name;
ArrayList<String> toppings;
public void prepare() {
System.out.println("준비중 " + name);
for (int i = 0; i < toppings.size(); i++) {
System.out.println("토핑추가 : "+ toppings.get(i));
}
}
public void bake() {
System.out.println("굽는중.....");
}
public void cut() {
System.out.println("자르는중.....");
}
public void box() {
System.out.println("박싱중.....");
}
}
- nation 별로 다른 Product를 적용해보자.
public class KorBoolGoggiPizza extends Pizza {
public KorBoolGoggiPizza() {
this.name = "Korea Style Cutting Pizza";
}
}
public class IndiaBoolGoggiPizza extends Pizza {
public IndiaBoolGoggiPizza() {
this.name = "India Style No cutting Pizza";
}
@Override
public void cut() {
System.out.println("인도에서는 피자를 컷팅하지 않아요");
}
}
- 위와같은 abstract Pizza class로 인해, Localize한 피자를 생산할 수 있게 되었다.
- 실제로 다른 nation 별 피자를 생성해보자.
public class PizzaStoreMain {
public static void main(String[] args) {
PizzaStore korStore = new KorPizzaStore();
PizzaStore indiaStore = new IndiaPizzaStore();
Pizza pizza = korStore.orderPizza("boolgogi");
System.out.println("완성" + pizza.getName() + "\n");
pizza = indiaStore.orderPizza("boolgogi");
System.out.println("완성" + pizza.getName() + "\n");
}
}
- 전체적인 다이어그램은 이런식으로 그려진다고 보면된다.
팩토리 메소드 패턴
객체를 생성하기 위한 interface(또는 abstract method)를 정의하게 되는데, 어떤클래스의 인스턴스를 만들지는 서브클래스에 의해서 결정되게 된다.
- 위 정의에서, "결정한다" 라고 표현한 이유는, 이 패턴을 사용할 때 서브클래스에서 실행중에 어떤 클래스의 인스턴스를 만들지 결정하기 때문이 아니라, 생산자 클래스 자체가 실제 생산될 제품에대한 사전지식이 전혀 없이 만들어지기 때문이다.
- 저금더 풀어서 표현하자면, "사용하는 서브클래스에 따라 생산되는 개체 인스턴스가 결정된다" 이다.
팩토리 패턴을 몰랐을때로 돌아가볼까?
public class DependentPizzaStore {
public Pizza createPizza(String nation, String type) {
Pizza pizza = null;
if (nation.equals("kor")) {
if(type.equals("bollgogi")) {
pizza = new KorBoolGoggiPizza();
} else if (type.equals("cheese")) {
pizza = new KorCheesePizza();
} else if (type.equals("veggie")) {
pizza = new KorVeggiePizza();
}
} else if (nation.equals("india")) {
if(type.equals("bollgogi")) {
pizza = new IndiaBoolGoggiPizza();
} else if (type.equals("cheese")) {
pizza = new IndiaCheesePizza();
} else if (type.equals("veggie")) {
pizza = new IndiaVeggiePizza();
}
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza
}
}
- 나중에 local이 많아지면 많아질수록 더욱 힘들어질것이다. 왜냐하면, PizzaStore 코드또한 변경되기 때문이다. 의존성에 대하여 다이어그램으로 표현해보자.
- 이제는 구상 클래스에 의존성을 줄이는것이 좋다는 것을 확실히 알 수 있다. 이런내용의 디자인 원칙을 표현하는 말이 있다..
"의존성 뒤집기 원칙(Dependency Inversion Principle)" 이라고 한다.
디자인 원칙
추상화된 것에 의존하도록 만들어라, 구상클래스에 의존하도록 만들지 않도록 한다.
- 이 원칙에서는 고수준 구성요소가 저수준 고성요소에 의존하면 안된다는 의미가 내포되어 있다. 이게 무슨말일까?
- PizzaStore는 고수준 구성요소, Pizza class들은 저수준 구성요소 라고 할 수 있다.
- 고수준 구성요소는 다른 저수준 구성요소에 의해 정의되는 행동이 들어있는 구성요소를 뜻한다. 예를들어 PizzaStore의 메소드는 Pizza에 의해 정의되어진다. PizzaStore는 피자를 만들고 또한 피자를 준비,굽기,자르고,포장한다.
- 디자인 원칙을 적용해 우리는 이렇게 멋진 디자인 다이어그램을 만들어냈다.!!
- 피자를 Localize하다보니 또하나 문제가 생겼다. 같은 boolgogi 피자인데, 인도에서는 소고기를 먹지않아 치킨불고기 재료를 써야하고, 또한 피자치즈는 염소치즈를 쓴다고한다.
- 위 문제를 추상 팩토리 패턴을 써서 해결하는법을 한번 알아보자.
'To be Developer > DesignPatterns' 카테고리의 다른 글
5.Singleton Pattern (0) | 2020.01.07 |
---|---|
4-2 Factory Pattern (Abstract Factory Pattern) (0) | 2020.01.05 |
3.Decorator Pattern (0) | 2020.01.02 |
2.Observer Pattern (0) | 2020.01.01 |
1.Strategy Pattern (0) | 2019.12.31 |