天天看點

設計模式(20):外觀模式Facade

1. 概述

     外觀模式,我們通過外觀的包裝,使應用程式隻能看到外觀對象,而不會看到具體的細節對象,這樣無疑會降低應用程式的複雜度,并且提高了程式的可維護性。

2. 問題

為了降低複雜性,常常将系統劃分為若幹個子系統。但是如何做到各個系統之間的通信和互相依賴關系達到最小呢?

比較自己泡茶和去茶館喝茶的差別,如果是自己泡茶需要自行準備茶葉、茶具和開水,如圖1(A)所示,而去茶館喝茶,最簡單的方式就是跟茶館服務員說想要一杯什麼樣的茶,是鐵觀音、碧螺春還是西湖龍井?正因為茶館有服務員,顧客無須直接和茶葉、茶具、開水等互動,整個泡茶過程由服務員來完成,

設計模式(20):外觀模式Facade
顧客隻需與服務員互動即可,整個過程非常簡單省事.

3. 解決方案

外觀模式:為子系統中的一組接口提供一個一緻的界面, Facade模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。引入外觀角色之後,使用者隻需要直接與外觀角色互動,使用者與子系統之間的複雜關系由外觀角色來實作,進而降低了系統的耦合度。

4. 适用性

在遇到以下情況使用facade模式:

    1) 當你要為一個複雜子系統提供一個簡單接口時。子系統往往因為不斷演化而變得越來越複雜。大多數模式使用時都會産生更多更小的類。

    這使得子系統更具可重用性,也更容易對子系統進行定制,但這也給那些不需要定制子系統的使用者帶來一些使用上的困難。facade可以提供一個簡單的預設視圖,

    這一視圖對大多數使用者來說已經足夠,而那些需要更多的可定制性的使用者可以越過facade層。

    2) 客戶程式與抽象類的實作部分之間存在着很大的依賴性。引入 facade将這個子系統與客戶以及其他的子系統分離,可以提高子系統的獨立性 和可移植性。

    3) 當你需要建構一個層次結構的子系統時,使用 facade模式定義子系統中每層的入口點。如果子系統之間是互相依賴的,你可以讓它們僅通過facade進行通訊,進而簡化了它們之間的依賴關系。

5. 結構

設計模式(20):外觀模式Facade
設計模式(20):外觀模式Facade

6.構模組化式的組成

外觀角色(Facade):是模式的核心,他被客戶client角色調用,知道各個子系統的功能。同時根據客戶角色已有的需求預訂了幾種功能組合\

子系統角色(Subsystem classes):實作子系統的功能,并處理由Facade對象指派的任務。對子系統而言,facade和client角色是未知的,沒有Facade的任何相關資訊;即沒有指向Facade的執行個體。

客戶角色(client):調用facade角色獲得完成相應的功能。

7. 效果

Facade模式有下面一些優點:

1)對客戶屏蔽子系統元件, 減少了客戶處理的對象數目并使得子系統使用起來更加容易。通過引入外觀模式,客戶代碼将變得很簡單,與之關聯的對象也很少。 2)實作了子系統與客戶之間的松耦合關系,這使得子系統的元件變化不會影響到調用它的客戶類,隻需要調整外觀類即可。 3)降低了大型軟體系統中的編譯依賴性,并簡化了系統在不同平台之間的移植過程,因為編譯一個子系統一般不需要編譯所有其他的子系統。一個子系統的修改對其他子系統沒有任何影響,而且子系統内部變化也不會影響到外觀對象。 4)隻是提供了一個通路子系統的統一入口,并不影響使用者直接使用子系統類。 Facade模式的缺點 1) 不能很好地限制客戶使用子系統類,如果對客戶通路子系統類做太多的限制則減少了可變性和靈活性。 2) 在不引入抽象外觀類的情況下,增加新的子系統可能需要修改外觀類或用戶端的源代碼,違背了“開閉原則”。

8. 實作

我們使用開關的例子;
  1. <?php   
  2.  class SwitchFacade  
  3. {  
  4.     private $_light     = null;     //電燈  
  5.     private $_ac        = null;     //空調  
  6.     private $_fan       = null;     //電扇  
  7.     private $_tv        = null;     //電視  
  8.     public function __construct()  
  9.     {  
  10.         $this->_light = new Light();  
  11.         $this->_fan = new Fan();  
  12.         $this->_ac = new AirConditioner();  
  13.         $this->_tv = new Television();  
  14.     }  
  15.     public function method1($isOpen =1) {  
  16.         if ($isOpen == 1) {  
  17.             $this->_light->on();  
  18.             $this->_fan->on();  
  19.             $this->_ac->on();  
  20.             $this->_tv->on();  
  21.         }else{  
  22.             $this->_light->off();  
  23.             $this->_fan->off();  
  24.             $this->_ac->off();  
  25.             $this->_tv->off();  
  26.         }  
  27.     }  
  28.     public function method2() {  
  29.         if ($isOpen == 1) {  
  30.             $this->_fan->on();  
  31.             $this->_ac->on();  
  32.             $this->_tv->on();  
  33.         }else{  
  34.             $this->_fan->off();  
  35.             $this->_ac->off();  
  36.             $this->_tv->off();  
  37.         }  
  38.     }  
  39. }  
  40. class Light  
  41. {     
  42.     private $_isOpen = 0;  
  43.     public function on() {  
  44.         echo 'Light is open', '<br/>';  
  45.         $this->_isOpen = 1;   
  46.     }  
  47.     public function off() {  
  48.         echo 'Light is off', '<br/>';  
  49.         $this->_isOpen = 0;  
  50.     }  
  51. }  
  52. class Fan  
  53. {  
  54.     private $_isOpen = 0;  
  55.     public function on() {  
  56.         echo 'Fan is open', '<br/>';  
  57.         $this->_isOpen = 1;   
  58.     }  
  59.     public function off() {  
  60.         echo 'Fan is off', '<br/>';  
  61.         $this->_isOpen = 0;  
  62.     }  
  63. }  
  64. class AirConditioner  
  65. {  
  66.     private $_isOpen = 0;  
  67.     public function on() {  
  68.         echo 'AirConditioner is open', '<br/>';  
  69.         $this->_isOpen = 1;   
  70.     }  
  71.     public function off() {  
  72.         echo 'AirConditioner is off', '<br/>';  
  73.         $this->_isOpen = 0;  
  74.     }  
  75. }  
  76. class Television  
  77. {  
  78.     private $_isOpen = 0;  
  79.     public function on() {  
  80.         echo 'Television is open', '<br/>';  
  81.         $this->_isOpen = 1;   
  82.     }  
  83.     public function off() {  
  84.         echo 'Television is off', '<br/>';  
  85.         $this->_isOpen = 0;  
  86.     }  
  87. }  
  88. class client {  
  89.     static function open() {  
  90.         $f = new  SwitchFacade();  
  91.         $f->method1(1);  
  92.     }  
  93.     static function close() {  
  94.         $f = new  SwitchFacade();  
  95.         $f->method1(0);  
  96.     }  
  97. }  
  98. client::open();  

  外觀模式中所指的子系統是一個廣義的概念,它可以是一個類、一個功能子產品、系統的一個組成部分或者一個完整的系統。子系統類通常是一些業務類,實作了一些具體的、獨立的業務功能,其典型代碼如下:

[csharp] view plaincopy

class SubSystemA  

{  

    public void MethodA()  

    {  

        //業務實作代碼  

    }  

}  

class SubSystemB  

{  

    public void MethodB()  

    {  

        //業務實作代碼  

     }  

}  

class SubSystemC  

{  

    public void MethodC()  

    {  

        //業務實作代碼  

    }  

}  

      在引入外觀類之後,與子系統業務類之間的互動統一由外觀類來完成,在外觀類中通常存在如下代碼:

[csharp] view plaincopy

class Facade  

{  

    private SubSystemA obj1 = new SubSystemA();  

    private SubSystemB obj2 = new SubSystemB();  

    private SubSystemC obj3 = new SubSystemC();  

    public void Method()  

    {  

        obj1.MethodA();  

        obj2.MethodB();  

        obj3.MethodC();  

    }  

}  

      由于在外觀類中維持了對子系統對象的引用,用戶端可以通過外觀類來間接調用子系統對象的業務方法,而無須與子系統對象直接互動。引入外觀類後,用戶端代碼變得非常簡單,典型代碼如下:

[csharp] view plaincopy

class Program  

{  

    static void Main(string[] args)  

    {  

        Facade facade = new Facade();  

        facade.Method();  

    }  

}  

11. 與其他相關模式

     1)抽象工廠模式:Abstract Factory式可以與Facade模式一起使用以提供一個接口,這一接口可用來以一種子系統獨立的方式建立子系統對象。 Abstract Factory也可以代替Facade模式隐藏那些與平台相關的類。

     2)中介模式:Mediator模式與Facade模式的相似之處是,它抽象了一些已有的類的功能。然而,Mediator的目的是對同僚之間的任意通訊進行抽象,通常集中不屬于任何單個對象的功能。

    Mediator的同僚對象知道中介者并與它通信,而不是直接與其他同類對象通信。相對而言,Facade模式僅對子系統對象的接口進行抽象,進而使它們更容易使用;它并不定義新功能,子系統也不知道Facade的存在。 

    通常來講,僅需要一個Facade對象,是以Facade對象通常屬于Singleton模式。

     3)Adapter模式:

    擴充卡模式是将一個接口通過适配來間接轉換為另一個接口。

    外觀模式的話,其主要是提供一個整潔的一緻的接口給用戶端。

12. 總結

1)根據 “單一職責原則”,在軟體中将一個系統劃分為若幹個子系統有利于降低整個系統的複雜性,一個常見的設計目标是使子系統間的通信和互相依賴關系達到最小,而達到該目标的途徑之一就是引入一個外觀對象,它為子系統的通路提供了一個簡單而單一的入口。

2)外觀模式也是“迪米特法則”的展現,通過引入一個新的外觀類可以降低原有系統的複雜度,外觀類充當了客戶類與子系統類之間的“第三者”,同時降低客戶類與子系統類的耦合度。外觀模式就是實作代碼重構以便達到“迪米特法則”要求的一個強有力的武器。

3) 外觀模式要求一個子系統的外部與其内部的通信通過一個統一的外觀對象進行,外觀類将用戶端與子系統的内部複雜性分隔開,使得用戶端隻需要與外觀對象打交道,而不需要與子系統内部的很多對象打交道。

4) 外觀模式從很大程度上提高了用戶端使用的便捷性,使得用戶端無須關心子系統的工作細節,通過外觀角色即可調用相關功能。 5)不要試圖通過外觀類為子系統增加新行為 ,不要通過繼承一個外觀類在子系統中加入新的行為,這種做法是錯誤的。外觀模式的用意是為子系統提供一個集中化和簡化的溝通管道,而不是向子系統加入新的行為,新的行為的增加應該通過修改原有子系統類或增加新的子系統類來實作,不能通過外觀類來實作。

13.模式擴充

一個系統有多個外觀類:         在外觀模式中,通常隻需要一個外觀類,并且此外觀類隻有一個執行個體,換言之它是一個 單例類。 在很多情況下為了節約系統資源,一般将外觀類設計為單例類。當然這并不意味着在整個系統裡隻能有一個外觀類, 在一個系統中可以設計多個外觀類,每個外觀類都負責和一些特定的子系統互動,向使用者提供相應的業務功能。 不要試圖通過外觀類為子系統增加新行為:        不要通過繼承一個外觀類在子系統中加入新的行為,這種做法是錯誤的。外觀模式的用意是為子系統提供一個集中化和簡化的溝通管道,而不是向子系統加入新的行為,新的行為的增加應該通過修改原有子系統類或增加新的子系統類來實作,不能通過外觀類來實作。 外觀模式與迪米特法則:        外觀模式創造出一個外觀對象,将用戶端所涉及的屬于一個子系統的協作夥伴的數量減到最少,使得用戶端與子系統内部的對象的互相作用被外觀對象所取代。外觀類充當了客戶類與子系統類之間的“第三者”,降低了客戶類與子系統類之間的耦合度,外觀模式就是實作代碼重構以便達到“迪米特法則”要求的一個強有力的武器。

抽象外觀類的引入:

外觀模式最大的缺點在于違背了 “開閉原則”, 當增加新的子系統或者移除子系統時需要修改外觀類,可以通過引入抽象外觀類在一定程度上解決該問題,用戶端針對抽象外觀類進行程式設計。對于新的業務需求,不修改原有外觀類,而對應增加一個新的具體外觀類,由新的具體外觀類來關聯新的子系統對象,同時通過修改配置檔案來達到不修改源代碼并更換外觀類的目的。 

UML:

設計模式(20):外觀模式Facade

繼續閱讀