2016년 4월 1일 금요일

[디자인패턴] 팩토리 메소드 패턴 ( Factory Method )

팩토리 패턴은 대표적인 생성 패턴 중 하나로 느슨한 결합을 이용하는 디자인입니다.

객체의 인스턴스를 만드는 작업이 항상 공개되어있어야 하는 것은 아니며, 공개를 할 때 객체간 결합의 문제가 발생할 수도 있습니다. 팩토리 패턴을 이용해 불필요한 의존성을 없앨 수 있습니다.

구조를 설계할 때 인터페이스에 맞춰서 코딩하면 변화에 열려있는 코드가 됩니다.
패턴에서 가장 중요한 것중 하나는 캡슐화라는 개념으로 바뀌는 부분과 바뀌지 않는 부분을 분리하고, 바뀌는 부분을 묶어두는 것입니다.
팩토리에서의 캡슐화는 생성을 묶어놓습니다.

간단한 팩토리를 보겠습니다.


  1. Pizza OrderPizza(char* pType)  
  2. {  
  3.     Pizza m_pizza;  
  4.   
  5.     // 피자 생성  
  6.     if(strcmp(pType,"치즈"))  
  7.     {  
  8.         m_pizza = new CheesePizza();  
  9.     }  
  10.     else if(strcmp(pType,"햄"))  
  11.     {  
  12.         m_pizza = new HamPizza();  
  13.     }  
  14.     else if(strcmp(pType,"파인애플"))  
  15.     {  
  16.         m_pizza = new PinePizza();  
  17.     }  
  18.   
  19.     // 준비  
  20.     m_pizza.Prepare();  
  21.   
  22.     // 굽기  
  23.     m_pizza.Bake();  
  24.   
  25.     // 자르기  
  26.     m_pizza.Cut();  
  27.   
  28.     // 포장  
  29.     m_pizza.Package();  
  30.   
  31.     return m_pizza;  
  32. }  


위 코드에서 피자 생성부분을 보겠습니다. 메뉴를 추가하거나 삭제할 때 이 부분이 주로 변경되고, 그 외의 부분은 변경되지 않습니다.
변경되는 부분과 변경되지 않는 부분을 알았으니 변경되는 부분을 캡슐화 해보겠습니다.


  1. class SimplePizzaFactory  
  2. {  
  3. public:  
  4.     Pizza CreatePizza(char* pType)  
  5.     {  
  6.         Pizza m_pizza;  
  7.   
  8.         // 피자 생성  
  9.         if(strcmp(pType,"치즈"))  
  10.         {  
  11.             m_pizza = new CheesePizza();  
  12.         }  
  13.         else if(strcmp(pType,"햄"))  
  14.         {  
  15.             m_pizza = new HamPizza();  
  16.         }  
  17.         else if(strcmp(pType,"파인애플"))  
  18.         {  
  19.             m_pizza = new PinePizza();  
  20.         }  
  21.   
  22.         return m_pizza;  
  23.     }  
  24. };  
  25.   
  26. class PizzaStore  
  27. {  
  28. private:  
  29.     SimplePizzaFactory factory;  
  30. public:  
  31.     PizzaStore(SimplePizzaFactory _factory)  
  32.     {  
  33.         factory = _factory;  
  34.     }  
  35.     Pizza OrderPizza(char* pType)  
  36.     {  
  37.         Pizza m_pizza;  
  38.   
  39.         // 피자 생성  
  40.         m_pizza = factory.CreatePizza(pType);  
  41.   
  42.         // 준비  
  43.         m_pizza.Prepare();  
  44.   
  45.         // 굽기  
  46.         m_pizza.Bake();  
  47.   
  48.         // 자르기  
  49.         m_pizza.Cut();  
  50.   
  51.         // 포장  
  52.         m_pizza.Package();  
  53.   
  54.         return m_pizza;  
  55.     }  
  56. };  


SimplePizzaFactory 클래스에서 하는 일은 피자를 생성하는 일 뿐입니다.
이렇게 피자를 생성하는 작업을 한 클래스에 캡슐화 시켜놓으면 어떤 구현을 변경해야 하는경우 이 팩토리 클래스만 변경하면 되기 때문에 보다 변화에 열려있는 코드가 됩니다.

PizzaStore 클래스를 보면 생성자에 팩토리 객체 전체가 전달됩니다.  생성 부분에서는 그냥 팩토리를 써서 피자 객체를 만드는 것을 볼 수 있습니다.
new 연산자 대신 팩토리 객체에 있는 CreatePizza 함수를 사용했기 때문에 인스턴스를 만들 필요가 없습니다.


지금까지 보신 것은 간단한 팩토리로 이는 디자인 패턴이라고 할 수는 없습니다. 하지만 팩토리 패턴에 대한 이해를 위해 짚고 넘어가는 것이 좋습니다.


SimplePizzaFactory 로 분리되기 전 코드에는 피자를 만드는 코드가 PizzaStore와 연결되긴 했지만 유연하진 못했습니다. 팩토리 메소드 패턴을 이용하면 이를 해결할 수 있습니다.





PizzaStore 클래스의 주문 과정은 모든 피자가게에서 동일하게 이루어진다고 할 때, 달라지는 것은 계속 말했듯이 생성입니다.
PizzaStore의 서브 클래스(자식 클래스)를 만들어 그 내부에서 CreatePizza() 를 구현한다면 자식 클래스마다 다른 스타일의 피자를 생성할 수 있게됩니다. 만약 새로운 스타일의 피자를 추가 생성하고 싶다면, 클래스를 추가하면되고, 삭제하고 싶다면 클래스를 삭제하면 되는 것입니다.


  1. // PizzaStore.h  
  2. #pragma once  
  3.   
  4. class Pizza;  
  5.   
  6. // 피자 가게  
  7. class PizzaStore  
  8. {  
  9. private:  
  10.     // 피자 생성  
  11.     virtual Pizza*  CreatePizza(char* pType);  
  12. public:  
  13.     // 피자 주문  
  14.     Pizza*          OrderPizza(char* pType);  
  15. };  
  16.   
  17. // 뉴욕 피자 가게  
  18. class NYPizzaStore : public PizzaStore  
  19. {  
  20. private:  
  21.     Pizza*  CreatePizza(char* pType);  
  22. };  
  23.   
  24. // 시카고 피자 가게  
  25. class ChicagoPizzaStore : public PizzaStore  
  26. {  
  27. private:  
  28.     Pizza*  CreatePizza(char* pType);  
  29. };  


  1. // PizzaStore.cpp  
  2. #include <iostream>  
  3. #include "Pizza.h"  
  4. #include "PizzaStore.h"  
  5.   
  6. using namespace std;  
  7.   
  8. // 피자 가게  
  9. // 피자 생성  
  10. Pizza*      PizzaStore::CreatePizza(char* pType)  
  11. {  
  12.     Pizza* pizza = NULL;  
  13.     return pizza;  
  14. }  
  15. // 피자 주문  
  16. Pizza*      PizzaStore::OrderPizza(char* pType)  
  17. {  
  18.     Pizza* pizza = NULL;  
  19.     // 피자 주문하는 곳에서 피자를 생성하게 한다.  
  20.     pizza = CreatePizza(pType);  
  21.   
  22.     // 준비  
  23.     pizza->Prepare();  
  24.     // 굽기  
  25.     pizza->Bake();  
  26.     // 커팅  
  27.     pizza->Cut();  
  28.     // 포장  
  29.     pizza->Package();  
  30.   
  31.     return pizza;  
  32. }  
  33.   
  34. // 뉴욕 피자 가게  
  35. Pizza*  NYPizzaStore::CreatePizza(char* pType)  
  36. {  
  37.     Pizza* pizza = NULL;  
  38.   
  39.     if(!strcmp(pType,"치즈"))  
  40.     {  
  41.         pizza = new NYStyleCheesePizza;  
  42.     }  
  43.     else if(!strcmp(pType,"햄"))  
  44.     {  
  45.         pizza = new NYStyleHamPizza;  
  46.     }  
  47.     else if(!strcmp(pType,"조개"))  
  48.     {  
  49.         pizza = new NYStyleClamPizza;  
  50.     }  
  51.     else if(!strcmp(pType,"파인애플"))  
  52.     {  
  53.         pizza = new NYStylePinePizza;  
  54.     }  
  55.     else  
  56.     {  
  57.         cout <<"* 메뉴가 없습니다."<< endl;  
  58.     }  
  59.   
  60.     return pizza;  
  61. }  
  62.   
  63. // 시카고 피자 가게  
  64. Pizza*  ChicagoPizzaStore::CreatePizza(char* pType)  
  65. {  
  66.     Pizza* pizza = NULL;  
  67.   
  68.     if(!strcmp(pType,"치즈"))  
  69.     {  
  70.         pizza = new ChicagoStyleCheesePizza;  
  71.     }  
  72.     else if(!strcmp(pType,"햄"))  
  73.     {  
  74.         pizza = new ChicagoStyleHamPizza;  
  75.     }  
  76.     else if(!strcmp(pType,"조개"))  
  77.     {  
  78.         pizza = new ChicagoStyleClamPizza;  
  79.     }  
  80.     else if(!strcmp(pType,"파인애플"))  
  81.     {  
  82.         pizza = new ChicagoStylePinePizza;  
  83.     }  
  84.     else  
  85.     {  
  86.         cout <<"* 메뉴가 없습니다."<< endl;  
  87.     }  
  88.   
  89.     return pizza;  
  90. }  


 OrderPizza 함수에서는 Pizza 객체를 가지고 여러 작업을 하지만 실제로 어떤 클래스에서 작업이 처리되고 있는지는 전혀 알 수가 없습니다. 이 말은 즉 PizzaStore와 Pizza클래스가 완전히 분리되있는 것을 말합니다.

NYPizzaStore에서 주문을 하면 뉴욕 스타일 피자가 만들어질 것이고, ChicagoPizzaStore에서 주문을 하면 시카고 스타일 피자가 만들어질 것입니다. 이것이 서브 클래스에서 실시간으로 결정하는 것을 의미하진 않습니다.
NYPizzaStore를 선택했다면 OrderPizza() 함수 입장에서는 그 서브클래스에서 피자의 종류를 결정한다고 할 수 있을 것입니다.

실제로는 우리가 선택하는 PizzaStore의 서브 클래스의 종류에 따라 결정되지만, 만들어지는 피자의 종류를 해당 서브클래스에서 결정한다고 할 수 있습니다.


모든 팩토리 패턴에서는 객체 생성을 캡슐화 합니다. 팩토리 메소드 패턴에서는 서브 클래스가 어떤 클래스를 만들지 결정하게 함으로써 객체 생성을 캡슐화 합니다. 






위 다이어그램에서 보면 PizzaStore가 있고, 그 서브클래스로 NY, Chicago가 있습니다. 또한 여러 피자 클래스들이 있습니다.

PizzaStore는 추상 클래스로 서브클래스에서 객체를 생성할 때 사용할 함수의 이름을 정의합니다. 하위 서브 클래스의 CreatePizza 함수가 팩토리 멧드 입니다. 이 함수에서 객체를 생산합니다. 각각 서브클래스가 따로 존재하기 때문에 CreatePizza 함수를 통해 다양하게 구현할 수 있습니다. 

Pizza 클래스는 팩토리로 제품을 생산하는 용도로 사용됩니다.


팩토리 메소드 패턴에서는 객체를 생성하기 위한 인터페이스(위에서는 PizzaStore)를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스(NY,Chicago PizzaStore)에서 결정하게 되고, 클래스의 인스턴스를 생성하는 일을 서브클래스에서 하게됩니다.

맨 처음에 보았던 간단한 팩토리는 팩토리 메소드 패턴과 꽤 비슷하나 일회용에 불과하기때문에 유연하지 않습니다. 반면에 팩토리 메소드 패턴을 이용하면 어던 구현을 사용할지를 서브클래스에서 결정하는 프레임워크를 만들 수 있다는 결정적인 차이가 있습니다.

팩토리 메소드 패턴은 구체적으로 어떤 클래스의 객체를 생성할지 미리 알지 못할 경우에 유용합니다. 계속 말했듯이 팩토리 메소드 패턴은 서브클래스를 추가해나가는 형태니까요. 때문에 어떤 객체를 생성할 것인지 상관없이 프로그래밍이 가능하며, 직접 생성자를 호출해 객체를 생성하는 것보다는 유연하고 확장성있는 구조입니다. 

단점이라고 말하면 생성할 객체의 종류가 달라질때 마다 새로운 하위클래스를 정의해야 하므로 클래스가 많아진다는 것이 있겠습니다.


추상 팩토리 패턴과 팩토리 메소드 패턴의 차이를 알아두면 좋습니다. 둘다 객체를 만드는 일을 하지만, 팩토리 메소드 패턴은 상속을 통해 객체를 만들고, 추상 팩토리 패턴은 객체 구성을 통해 만듭니다. 추상 팩토리 패턴은 새로운 것을 추가하려면 인터페이스를 바꿔야하고, 훨씬 더 크고 복잡한 인터페이스를 가지고 있습니다. 


아래 코드는 팩토리 메소드 패턴을 C++로 구현한 것입니다

댓글 없음:

댓글 쓰기

[Effective C++] 항목 30 : 인라인 함수는 미주알고주알 따져서 이해해 두자.

인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기가 용이해집니다. 인라인 함수의 아이디어는  함수 호출문을 그 함수의 본문으로 바꿔치기하자는 것  남발했다가는 코드의 크기가 커질 게 뻔하다. 인라인 함수로 부풀려진 ...