2016년 4월 1일 금요일

[디자인패턴] 퍼사드 패턴 (Facade Pattern)

Facade의 뜻은 정면, 표면이라는 뜻입니다. 

퍼사드 패턴은 어떤 서브 시스템의 일련의 인터페이스에 대한 통합된 인터페이스를 제공하고, 퍼사드에서 고수준 인터페이스를 정의하기 때문에 서브 시스템을 더 쉽게 사용할 수 있습니다.

퍼사드 패턴을 만들기 위해서는 어떤 서브 class에 속한 일련의 복잡한 클래스들을 단순화하고, 통합한 클래스를 만들어야 합니다.
다른 패턴과 달리 추상화같은게 필요없어 단순한 편으로, 퍼사드를 이용하면 클라이언트와 서브시스템이 긴밀하게 연결되지 않아도 되고, 객체지향의 원칙을 준수하는데 도움이 됩니다. 


간단히 말하면 단순화된 인터페이스를 통해 서브시스템을 더 쉽게 사용하게 합니다.


퍼사드 패턴의 장점은 더 간단한 인터페이스를 만들 수 있다는 것이 있습니다. 또한 클라이언트 구현과 서브시스템을 분리할 수 있습니다. 예를 들어 인터페이스가 달라졌을 때 만약 클라이언트를 퍼사드로 만들었다면 클라이언트는 변경할 필요없이 퍼사드만 변경하면 될 것입니다. 
또한 표면에 드러나있는 인터페이스를 사용하고 그 내부는 알 필요가 없게 됩니다.

코드를 통해 이해하면 더 쉬울 것이니 코드를 보겠습니다.

  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. // 컴퓨터  
  5. class Computer  
  6. {  
  7. public:  
  8.     void    On(void)    {   cout << "컴퓨터 On" << endl; }  
  9.     void    Off(void)   {   cout << "컴퓨터 Off" << endl; }  
  10. };  
  11. // 모니터  
  12. class Monitor  
  13. {  
  14. public:  
  15.     void    On(void)    {   cout << "모니터 On" << endl; }  
  16.     void    Off(void)   {   cout << "모니터 Off" << endl; }  
  17. };  
  18. // 스피커  
  19. class Speaker  
  20. {  
  21. public:  
  22.     void    On(void)    {   cout << "스피커 On" << endl; }  
  23.     void    Off(void)   {   cout << "스피커 Off" << endl; }  
  24. };  
  25. // Facade 패턴  
  26. class Facade  
  27. {  
  28. private:  
  29.     Computer*   m_Computer;  
  30.     Monitor*    m_Monitor;  
  31.     Speaker*    m_Speaker;  
  32. public:  
  33.     Facade(Computer* _computer,Monitor* _monitor,Speaker* _speaker)  
  34.     {  
  35.         this->m_Computer = _computer;  
  36.         this->m_Monitor      = _monitor;  
  37.         this->m_Speaker      = _speaker;  
  38.     }  
  39.     // 컴퓨터 켜기  
  40.     void        PowerOn(void)  
  41.     {  
  42.         cout << "컴퓨터 파워 On" << endl;  
  43.         m_Computer->On();  
  44.         m_Monitor->On();  
  45.         m_Speaker->On();  
  46.     }  
  47.     // 컴퓨터 끄기  
  48.     void        PowerOff(void)  
  49.     {  
  50.         cout << "컴퓨터 파워 Off" << endl;  
  51.         m_Computer->Off();  
  52.         m_Monitor->Off();  
  53.         m_Speaker->Off();  
  54.     }  
  55. };  
  56.   
  57. int main(void)  
  58. {  
  59.     Computer    computer;  
  60.     Monitor     monitor;  
  61.     Speaker     speaker;  
  62.   
  63.     Facade      m_ComFacade(&computer,&monitor,&speaker);  
  64.   
  65.     m_ComFacade.PowerOn();  
  66.     cout << "==============" << endl;  
  67.     m_ComFacade.PowerOff();  
  68.   
  69.     return 0;  
  70. }  




위 코드는 간단한 퍼사드 패턴에 대해 나타내고 있습니다. 

예를 들어, 컴퓨터를 킨다고 할 때, 파워를 누르면 컴퓨터, 모니터, 스피커도 켜져야 합니다. 여러 서브 클래스 일일히 할당해 줄 필요없이 Facade 클래스 하나만 PowerOn() 해주면 그에 따른 서브시스템이 알아서 동작하게 됩니다.

이것이 퍼사드 패턴입니다.


이를 좀 더 유용한 예로 보겠습니다.

  1. #include <iostream>  
  2.   
  3. using namespace std;  
  4.   
  5. // 애니메이션  
  6. class Animation  
  7. {  
  8. public:  
  9.     void    Play(void)  
  10.     {  
  11.         cout << "애니메이션 Play" << endl;  
  12.     }  
  13. };  
  14.   
  15. // 카메라  
  16. class Camera  
  17. {  
  18. public:  
  19.     void    Walking(void)  
  20.     {  
  21.         cout << "카메라 Walking" << endl;  
  22.     }  
  23. };  
  24.   
  25. class Player  
  26. {  
  27. private:  
  28.     Camera*     m_pCamera;  
  29.     Animation*  m_pAnimation;  
  30.   
  31.     float       m_fz;  
  32.     float       m_fSpeed;  
  33. public:  
  34.     // 생성자  
  35.     Player(Camera* camera,Animation* animation,float fSpeed)  
  36.     {  
  37.         m_fz                = 0;  
  38.         this->m_pCamera      = camera;  
  39.         this->m_pAnimation   = animation;  
  40.         this->m_fSpeed       = fSpeed;  
  41.     }  
  42.     void    Walking(void)  
  43.     {  
  44.         cout << "플레이어 Walking 시작" << endl;  
  45.       
  46.         m_fz    += m_fSpeed;        // 플레이어 좌표가 움직이면  
  47.         m_pCamera->Walking();        // Camera도 따라서 움직여야 하고,  
  48.         m_pAnimation->Play();        // 걷는 Animation도 나타나야 한다.  
  49.     }  
  50. };  
  51.   
  52. int main(void)  
  53. {  
  54.     Camera      camera;  
  55.     Animation   animation;  
  56.   
  57.     Player      player(&camera,&animation,5.0f);  
  58.       
  59.     // 플레이어 걷기  
  60.     player.Walking();  
  61.   
  62.     return 0;  
  63. }  




플레이어가 있습니다. 플레이어 움직임에 해당하는 애니메이션 클래스와 카메라 클래스가 있습니다.

플레이어가 움직여 좌표가 변경되면 카메라도 따라서 Walking 해야 합니다.  
위와 같이 짜면 플레이어가 걷는 행동을 하는 것에 대한 애니메이션과 카메라 이동이 어떻게 동작하는지 신경써주지 않아도 된다는 장점이 있습니다. 만약 이렇게 설계하지 않는다면 이동시 마다 애니메이션과 카메라 클래스를 따로 조작해줘야하는 번거로움이 생깁니다.


최소 지식 원칙(= 데메테르의 법칙 Law of Demeter)이라는 것이 있습니다. 
이는 시스템을 디자인할 때, 어떤 객체든 그 객체와 상호작용을 하는 클래스의 개수에 주의해야 하며, 객체들과 어떤식으로 상호작용하는지에도 주의를 기울여야 한다는 뜻입니다. 이것을 이용해 여러 클래스들이 복잡하게 얽혀서 시스템의 한 부분을 변경했을 때 다른 부분까지 고쳐야 되는 상황을 방지할 수 있습니다.

이 원칙을 잘 따르면 객체들 간의 의존성을 줄여 프로그램 관리가 용이해집니다. 하지만 다른 구성요소에 대한 메소드 호출을 처리하기 위해 추가로 클래스를 만들어야 할 수도 있으며 이는 시스템이 더 복잡해지고 개발 시간도 늘어나며 성능도 떨어질 수가 있습니다.


그렇다면 상호작용하는 클래스를 줄이면서 다른 객체에 영향력을 행사하는 방법에는 무엇이 있을까요?
어떤 함수던지 네 종류의 객체의 메소드만 호출하면 됩니다.

1. 객체 자체
2. 메소드에 매개변수로 전달된 객체
3. 그 메소드에서 생성하거나 인스턴스를 만든 객체
4. 그 객체에 속하는 구성요소

위에 따르면 다른 메소드를 호출해서 리턴 받은 객체의 메소드를 호출하는 것도 바람직하지 않습니다. 만약 이럴경우 다른 객체의 일부분에 대해 요청을 하게 되는 것이고, 이러면 직접적으로 알고 지내는 객체의 수가 늘어나게 됩니다. 이때 최소 지식원칙을 따르려면 그 객체 족에서 대신 요청을 하도록 만들어야 합니다.

바로 위 코드를 예로 들면 main 함수는 Player 함수에만 연결되어 있고, Player 함수는 main 함수를 대신해 서브시스템 구성요소를 관리해 줍니다. 따라서 main 이 좀 더 단순하고 유연해 질 수 있습니다.

서브시스템에서도 최소 지식 원칙을 이용해 퍼사드를 추가하면 좀 더 간단한 시스템이 될 수 있습니다.

댓글 없음:

댓글 쓰기

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

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