外觀模式是一種使用頻率很是高的結構型設計模式,它經過引入一個外觀角色來簡化客戶端與子系統之間的交互,爲複雜的子系統調用提供一個統一的入口,下降子系統與客戶端的耦合度,且客戶端調用很是方便。php
在軟件開發中,有時候爲了完成一項較爲複雜的功能,一個客戶類須要和多個業務類交互,而這些須要交互的業務類常常會做爲一個總體出現,因爲涉及到的類比較多,致使使用時代碼較爲複雜。此時,特別須要一個相似服務員同樣的角色,由它來負責和多個業務類進行交互,而客戶類只需與該類交互。外觀模式經過引入一個新的外觀類(Facade)來實現該功能,外觀類充當了軟件系統中的「服務員」,它爲多個業務類的調用提供了一個統一的入口,簡化了類與類之間的交互。在外觀模式中,那些須要交互的業務類被稱爲子系統(Subsystem)。若是沒有外觀類,那麼每一個客戶類須要和多個子系統之間進行復雜的交互,系統的耦合度將很大;而引入外觀類以後,客戶類只須要直接與外觀類交互,客戶類與子系統之間原有的複雜引用關係由外觀類來實現,從而下降了系統的耦合度。編程
外觀模式中,一個子系統的外部與其內部的通訊經過一個統一的外觀類進行,外觀類將客戶類與子系統的內部複雜性分隔開,使得客戶類只須要與外觀角色打交道,而不須要與子系統內部的不少對象打交道。設計模式
外觀模式定義以下:工具
外觀模式:爲子系統中的一組接口提供一個統一的入口。外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。
外觀模式又稱爲門面模式,它是一種對象結構型模式。外觀模式是迪米特法則的一種具體實現,經過引入一個新的外觀角色能夠下降原有系統的複雜度,同時下降客戶類與子系統的耦合度。this
外觀模式沒有一個通常化的類圖描述,下面所示的類圖能夠做爲描述外觀模式的結構圖:加密
外觀模式包含以下兩個角色:spa
引入外觀模式以後,增長新的子系統或者移除子系統都很是方便,客戶類無須進行修改(或者極少的修改),只須要在外觀類中增長或移除對子系統的引用便可。從這一點來講,外觀模式在必定程度上並不符合開閉原則,增長新的子系統須要對原有系統進行必定的修改,雖然這個修改工做量不大。設計
外觀模式中所指的子系統是一個廣義的概念,它能夠是一個類、一個功能模塊、系統的一個組成部分或者一個完整的系統。子系統類一般是一些業務類,實現了一些具體的、獨立的業務功能,其典型代碼以下:code
class SubSystemA { public function MethodA() { //業務實現代碼 } } class SubSystemB { public function MethodB() { //業務實現代碼 } } class SubSystemC { public function MethodC() { //業務實現代碼 } }
在引入外觀類以後,與子系統業務類之間的交互統一由外觀類來完成,在外觀類中一般存在以下代碼:對象
class Facade { private $obj1; private $obj2; private $obj3; public function __construct() { $this->obj1 = new SubSystemA(); $this->obj2 = new SubSystemB(); $this->obj3 = new SubSystemC(); } public function Method() { $this->obj1->MethodA(); $this->obj2->MethodB(); $this->obj3->MethodC(); } }
因爲在外觀類中維持了對子系統對象的引用,客戶端能夠經過外觀類來間接調用子系統對象的業務方法,而無須與子系統對象直接交互。
某軟件公司欲開發一個可應用於多個軟件的文件加密模塊,該模塊能夠對文件中的數據進行加密並將加密以後的數據存儲在一個新文件中,具體的流程包括三個部分,分別是讀取源文件、加密、保存加密以後的文件,其中,讀取文件和保存文件使用流來實現,加密操做經過求模運算實現。這三個操做相對獨立,爲了實現代碼的獨立重用,讓設計更符合單一職責原則,這三個操做的業務代碼封裝在三個不一樣的類中。現使用外觀模式設計該文件加密模塊。
經過分析,本實例結構圖如圖所示:
EncryptFacade充當外觀類,FileReader、CipherMachine和FileWriter充當子系統類。示例代碼以下:
<?php class FileReader { public function Read(string $fileNameSrc): string { return file_get_contents($fileNameSrc); } } class CipherMachine { public function Encrypt(string $plainText): string { return openssl_encrypt($plainText, 'DES-ECB', '123456'); } } class FileWriter { public function Write(string $encryptStr, string $fileNameDes) { file_put_contents($fileNameDes, $encryptStr); } } class EncryptFacade { //維持對其餘對象的引用 private $reader; private $cipher; private $writer; public function __construct() { $this->reader = new FileReader(); $this->cipher = new CipherMachine(); $this->writer = new FileWriter(); } //調用其餘對象的業務方法 public function FileEncrypt(string $fileNameSrc, string $fileNameDes) { $plainStr = $this->reader->Read($fileNameSrc); $encryptStr = $this->cipher->Encrypt($plainStr); $this->writer->Write($encryptStr, $fileNameDes); } }
在標準的外觀模式結構圖中,若是須要增長、刪除或更換與外觀類交互的子系統類,必須修改外觀類或客戶端的源代碼,這將違背開閉原則,所以能夠經過引入抽象外觀類來對系統進行改進,在必定程度上能夠解決該問題。在引入抽象外觀類以後,客戶端能夠針對抽象外觀類進行編程,對於新的業務需求,不須要修改原有外觀類,而對應增長一個新的具體外觀類,由新的具體外觀類來關聯新的子系統對象,同時經過修改配置文件來達到不修改任何源代碼並更換外觀類的目的。
若是在應用實例「文件加密模塊」中須要更換一個加密類,再也不使用原有的加密類CipherMachine,而改成新加密類NewCipherMachine。若是不增長新的外觀類,只能經過修改原有外觀類EncryptFacade的源代碼來實現加密類的更換,將原有的對CipherMachine類型對象的引用改成對NewCipherMachine類型對象的引用,這違背了開閉原則,所以須要經過增長新的外觀類來實現對子系統對象引用的改變。
若是增長一個新的外觀類NewEncryptFacade來與FileReader類、FileWriter類以及新增長的NewCipherMachine類進行交互,雖然原有系統類庫無須作任何修改,可是由於客戶端代碼中原來針對EncryptFacade類進行編程,如今須要改成NewEncryptFacade類,所以須要修改客戶端源代碼。
如何在不修改客戶端代碼的前提下使用新的外觀類呢?解決方法之一是:引入一個抽象外觀類,客戶端針對抽象外觀類編程,而在運行時再肯定具體外觀類。更換具體外觀類時只需修改配置文件,無須修改源代碼,符合開閉原則。
外觀模式是一種使用頻率很是高的設計模式,它經過引入一個外觀角色來簡化客戶端與子系統之間的交互,爲複雜的子系統調用提供一個統一的入口,使子系統與客戶端的耦合度下降,且客戶端調用很是方便。
外觀模式並不給系統增長任何新功能,它僅僅是簡化調用接口。在幾乎全部的軟件中都可以找到外觀模式的應用,如絕大多數B/S系統都有一個首頁或者導航頁面,大部分C/S系統都提供了菜單或者工具欄,在這裏,首頁和導航頁面就是B/S系統的外觀角色,而菜單和工具欄就是C/S系統的外觀角色,經過它們用戶能夠快速訪問子系統,下降了系統的複雜程度。全部涉及到與多個業務對象交互的場景均可以考慮使用外觀模式進行重構。