문제 제기

 - "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

+ Recent posts