프로그래밍 Design Pattern 이해하기 - 4 팩토리 패턴


Android/번역본 2014.08.14 22:49



팩토리 패턴


이번 시간엔 팩토리 패턴에 대해 알아보자.





 이 한줄을 보자. MallardDuck을 Concreate Class라고 부른다. (abstract class를 상속받아 구체화된 객체를 Concreate class라고 부른다!)



 위와같이 abstract class를 상속받아 서브클래싱을 하면, new MallardDuck();처럼. 이렇게 되면, runtime 도중에 Duck을 바꿀 수가 없게 된다. 쉽게 예를 들면 아래와 같이, 





매번 상태에 따라 Duck을 바꿀 때 객체를 만들어야 하며, 수없이 많은 조건들이 생겨나게 된다! 이런 코드들이 많아지면, 앱을 유지보수하는데 굉장히 어려워지며 또한 잘못고쳤을 때 독립성이 유지되지 않아 에러가 많이 일어나게 된다.


 "new"의 잘못된 점은 무엇일까?


 기술적으로는 "new"가 전혀 잘못된 것이 없다. (자바의 기본문법이니까!) 

Interface를 쓰게 되면, 앱이 구동되면서 수많은 변화에서 객체를 격리할 수 있다. (다른 클래스에서 어떤 변화가 일어날 때 다른 클래스가 영향을 받지 는다는 것) 코드가 interface에 기록되어 있는 경우 새로운 클래스를 만들어도 인터페이스를 상속받게 되고 이는 '다형성'이다!

abstract class가 아닌 모든 메서드가 구현된 슈퍼클래스를 상속받아 서브클래싱을 하게 되면, 뭔가 수정이 일어날 때 베이스가 되는 클래스를 수정해야 되기 때문에 다른 클래스에 미치는 영향이 커지게 된다(굉장히 찝찝하다!). 즉 "수정에 대해 닫혀있다"라는 이론을 위배하게 된다. 새로운 클래스들을 확장할 때마다 베이스가 되는 클래스(슈퍼클래스)를 열고 수정하게 될 것이다.


 그렇다면 어떻게 해야할까?


 항상 코드를 짤 때에 전에 이야기했던 "개방-폐쇄 원칙"을 꼭 따르려고 노력해야 한다. 이러한 패턴들보다 중요한 점은 "확장은 자유롭되,같은 것에 대해서는 분리하라(확장에는 열려있되, 수정에는 닫혀있어라)"이다. 


 

 팩토리 패턴에 대해 알아보자!


 잘라서 박스에 담아 판매하는 피자샵의 오너라고 생각해보자! 당신은 다음과 같이 코드를 짰다!




위 코드에서 Pizza pizza = new Pizza(); 이 부분을 보면, 우리가 앞서 이야기했듯이 pizza는 interface class를 상속받도록 만들어놓자!

만약 abstract class가 아닌, 이미 모든 것이 다 구현되어 있는 클래스라면, 코드는 아래와 같이 분기가 많아질 것이다. 




만약 "ClamPizza"와 "EggiePizza"가 생기고, "GreekPizza" 메뉴가 사라지면, 우리는 저 조건을 고쳐야 한다. (수정에 대해 닫혀있다는 원칙에 위배된다!) 


    if(type.eqauls("cheese"){

      pizza = new CheesePizza();

   }else if(type.eqauls("greek"){

      pizza = new GreekPizza();

   }else if(type.eqauls("clam"){

      pizza = new ClamPizza();

   }else if(type.eqauls("veggie"){

      pizza = new VeggiePizza();

   }


 그렇다면, 위의 조건 부분을 어떻게 변경해야 할까? 


새로운 객체를 만들어보자. 그리고 우리는 이 객체를 "팩토리"라고 부를 것이다. 

"SimplePizzaFactory" 라는 새로운 객체를 만들었다. 아래 코드를 따라가자. 



 

위와 같이 구현을 하면 얻는 이점은 무엇일까? 그저 문제점이 있는 코드를 다른 객체로 옮긴것 뿐 아닌가?


- 피자와 관련되어 구현되는 모든 곳에 적힐 코드를 SimplePizzaFactory 만 가지고 있게 된다. 우리는 orderPizza() 메서드 안에서 피자를 주문하는 코드만을 구현할 수 있다. 우리는 새로운 메뉴가 생기거나 사라질 때 오로지 이 클래스의 해당 부분만 수정하면 된다. pizza를 만들 수 있는 것을 캡슐화 했기 때문에 다른 곳에서 일어나는 수정으로부터 객체를 격리시킬 수 있다. 


이렇게 하면 static 메서드로 한 곳에서 선언하는 것과 비슷해보이는데, 무엇이 다른가? 


- Simple factory 클래스를 만드는 것은 static method로 선언해서 사용하는 것과 같은 기술이기도 하며 그 기법을 static factory 라고도 부르기도 한다. static method로 선언하는 이유가 다른 오브젝트에서 새로운 SimplePizzaFactory 객체를 생성하지 못하게 하고 한 곳에서만 처리하게 하기 위함이다. 때문에 둘다 만드는 이유는 같다. 하지만 static으로 선언하면 Subclass들을 만들 수 없다. (Factory를 상속받는 다른 Factory들을 만들수가 없게 된다) 



자 그러면 만든 클래스를 붙여보자. 









만약 피자샵이 프차이즈를 시작했다고 생각해보자!


모든 체인점에서 이 PizzaStore 코드를 가져다 써야 하는데, 이럴 때 프렌차이즈 체인점에서는 이 PizzaStore을 가져다가 쓰면된다!

NYPizzaFactory, ChicagoPizzaFactory 등등 다양한 클래스들이 PizzaStore만 가져다 쓰면 된다는 것!


NYPizzaFactory nyFactory = new NYPizzaFactory();

PizzaStore nyStore = new PizzaStore(nyFactory);

nyStore.order("Veggie");


그런데, 뉴욕피자스토어에서 뉴욕에서만 판매하는 3rd Party피자를 만들려고 한다면!? 

Pizza store 를 Framework 화 시켜보자. 


각각 프렌차이즈 지역의 특성에 맞게 피자들을 지역화하고 그들만의 피자들을 만들 수 있도록 아래와 같이 코드를 수정해보자.


public class PizzaStore {

public Pizza orderPizza(String type) {

Pizza pizza;


pizza = createPizza(type);


pizza.perpare();

pizza.bake();

pizza.cut();

pizza.box();

return pizza;

}

abstract createPizza(String type);

}


PizzaStore는 orderPizza()메소드가 가장 중요하다. 모든 프렌차이즈들은 이제 PizzaStore의 subClass로 동작이 가능하다. 





PizzaStore에 대한 코드들은 만들었는데, 생각해보니 정작 Pizza에 대한 코드는 짜지 않았다! 

다음은 "팩토리 메서드 패턴"에 대한 예제이다. 


특징은 

- 모든 팩토리 패턴에서는 객체 생성을 캡슐화 한다. 

- 객체 생성 코드를 전부 한 객체 또는 메서드에 넣으면 중복되는 내용을 제거할 수 있고, 관리에도 유용하다. 

- 인터페이스를 바탕으로 프로그래밍이 가능하다. 




자 이렇게 되면, 


public class NYStyleChessePizza extends Pizza {

public NYStyleCheesePizza() {

name = "NYCheesePizza";

dough = "Crust Dough";

sauce = "Plum Tomato Sauce";

}


void cut() {


}

}


그럼 완성된 코드들을 봅시다. 









이렇게 구현된 것을 병렬 클래스 계층 구조라고 한다. 


즉 

"객체를 생성하기 위한 인터페이스를 정의할 때, 어떤 클래스의 인스턴스를 만들지 서브클래스에서 결정한다!"(PizzaStore는 NYPizzaStore에서 만든다!)


PizzaStore는 Pizza라는 abstract class를 객체로 들고 있기 때문에 Concreate class들이 아무리 많아져도 영향을 받지 않게 된다!


이런 패턴의 장점으로는, 


- 객체 생성코드를 한 객체에 넣으면 중복되는 내용을 제거할 수 있다. 

- 클라이언트 입장에서는 객체 인스턴스를 만들 때 구상 클래스(Concreate Class)가 아닌, 인터페이스만 필요하게 된다.

- 인터페이스를 바탕으로 프로그래밍이 가능해 유연성과 확장성이 뛰어나다.


그럼 의존도에 대해 알아보자. 





그럼 의존도를 뒤집어보자!


의존도를 낮추기위해서 의존도를 뒤집을 때 주의해야 할 것은 다음과 같다.



- 의존성 뒤집기 원칙에 의하면, 구상클래스처럼 구체적인 것이 아닌 '추상적인 클래스나 인터페이스'와 같이 추상적인 것에 의존하는 코드를 만들어야 한다.


즉,


위 처럼 Pizza 클래스를 상위나 하위 모두 의존하고 있다. 물론 Pizza는 abstract하게 큰 구조만 가지고 있을 뿐 Concreate class는 아니다. PizzaStore는 abstract class인 Pizza만을 가지고 있기 때문에 Pizza를 가진 많은 Concreate class가 생겨도 영향을 받지 않게 된다.


추상팩토리 메서드 패턴은 위에서 팩토리클래스가 많아질 경우, 팩토리 클래스가 쓰는 메서드들을 모아놓은 또다른 abstract 클래스를 만들어 또다시 팩토리클래스들을 서브클래싱한다. 간단해서 아래 예제만 첨부.





그럼 정리를 해보자. 


- 팩토리를 쓰면 객체 생성을 캡슐화할 수 있다. 

- 간단한 팩토리는 엄밀히 디자인 패턴은 아니지만, 클라이언트와 구상 클래스를 분리시키기 위한 간단한 기법으로 사용이 가능하다. 

- 팩토리 메소드 패턴에서는 상속을 활용하되, 객체 생성을 서브클래스에게 위임되며, 서브클래스에서는 팩토리 메서드를 사용하여 객체를 만들어낸다. 

- 모든 팩토리 패턴에서는 애플리케이션의 구상클래스에 대한 의존성을 낮추므로 결합도를 낮춘다. 

- 어떤 클래스에서 인스턴스를 만드는 일을 팩토리에게 넘긴다. 

- 의존성 뒤집기 원칙을 따르면 슈퍼클래스에 의존을 피하고 추상화를 지향할 수 있다!





저작자 표시 비영리 변경 금지
신고

WRITTEN BY
ShakeJ

0 ,