1. 程式人生 > >從封裝變化的角度看設計模式——介面隔離

從封裝變化的角度看設計模式——介面隔離

## 封裝變化之介面隔離 在元件的構建過程當中,某些介面之間直接的依賴常常會帶來很多問題、甚至根本無法實現。採用新增一層間接(穩定)的介面,來隔離本來互相緊密關聯的介面是一種常見的解決方案。 這裡的介面隔離不同於介面隔離原則,介面隔離原則是對介面職責隔離,也就是儘量減少介面職責,使得一個類對另一個類的依賴應該建立在最小的介面上。 而這裡所講到的介面隔離是對依賴或者通訊關係的隔離,通過在原有系統中加入一個層次,使得整個系統的依賴關係大大的降低。而這樣的模式主要有外觀模式、代理模式、中介者模式和介面卡模式。 ## 外觀模式 - Facade Facade模式其主要目的在於為子系統中的一組介面提供一個一致的介面(介面),Facade模式定義了一個高層介面,這個介面使得更加容易使用。 在我們對系統進行研究的時候,往往會採用抽象與分解的思路去簡化系統的複雜度,因此在這個過程當中就將一個複雜的系統劃分成為若干個子系統。也正是因為如此,子系統之間的通訊與相互依賴也就增加了,為了使得這種依賴達到最小,Facade模式正好可以解決這種問題。 Facade模式體現的更多的是一種介面隔離的思想,它體現在很多方面上,最常見的比如說使用者圖形介面、作業系統等。這都可以體現這樣一個思想。 ![facade.png](https://img2020.cnblogs.com/other/1218435/202007/1218435-20200711143040555-1983625189.png) Facade模式從結構上可以簡化為上面這樣一種形式,但其形式並不固定,尤其是體現在其內部子系統的關係上,因為其內部的子系統關係肯定是複雜多樣的,並且`SubSystem`不一定是類或者物件,也有可能是一個模組,這裡只是用類圖來表現Facade模式與其子系統之間的關係。 從程式碼體現上來看,可以這樣表現: ```java public class SubSystem1 { public void operation1(){ //完成子系統1的功能 ...... } } public class SubSystem2 { public void operation2(){ //完成子系統2的功能 ...... } } public class SubSystem3 { public void operation3(){ //完成子系統3的功能 ...... } } public class SubSystem21 extends SubSystem2{ //對子系統2的擴充套件 ...... } public class SubSystem22 extends SubSystem2 { //對子系統2的擴充套件 ...... } ``` 上面子系統內部各部分的一個體現,如何結合Facade來對外隔離它的系統內部複雜依賴呢?看下面: ```java public class Facade { private SubSystem1 subSystem1; private SubSystem2 subSystem2; private SubSystem3 subSystem3; public Facade(){ subSystem1 = new SubSystem1(); subSystem2 = new SubSystem21(); subSystem3 = new SubSystem3(); } public void useSystem1(){ subSystem1.operation1(); } public void useSystem2(){ subSystem2.operation2(); } public void useSystem3(){ subSystem3.operation3(); } } ``` 當然,這只是Facade模式的一種簡單實現,可能在真正的實現系統中,會有著更加複雜的實現,比如各子系統之間可能存在依賴關係、又或者呼叫各子系統時需要傳遞引數等等,這些都會給Facade模式的實現帶來很大的影響。 ```java public class Client { public static void main(String[] args) { Facade facade = new Facade(); facade.useSystem1(); facade.useSystem2(); facade.useSystem3(); } } ``` 當存在Facade之後,客戶對子系統的訪問就只需要面對Facade,而不需要再去理解各子系統之間的複雜依賴關係。當然對於普通客戶而言,使用Facade所提供的介面自然是足夠的;對於更加高階的客戶而言,Facade模式並未遮蔽高階客戶對子系統的訪問,也就是說,如果有客戶需要根據子系統定製自己的功能也是可以的。 對Facade的理解很簡單,但是在具體使用時,又需要注意些什麼呢? + **進一步地降低客戶與子系統之間的耦合度。**具體實現是,使用抽象類來實現Facade而通過它的具體子類來應對不同子系統的實現,並且可以滿足客戶根據要求自己定製Facade。 除了使用子類的方式之外,通過其他的子系統來配置Facade也是一個方法,並且這種方法的靈活性更好。 + **在層次化結構中,可以使用外觀模式定義系統中每一層的入口。**剛才我們就提到過,`SubSystem`不一定只表示一個類,它包含的可能是一些類,並且是一些具有協作關係的類,那麼對於這些類,自然也是使用外觀模式來為其定義一個統一的介面。 + Facade模式自身也有缺點,雖然它減少系統的相互依賴,提高靈活性,提高了安全性;但是其本身就是不符合開閉原則的,如果子系統發生變化或者客戶需求變化,就會涉及到Facade的修改,這種修改是很麻煩的,因為無論是通過擴充套件或是繼承都可能無法解決,只能以修改原始碼的方式。 ## 代理模式 - Proxy 在Proxy模式中,我們建立具有現有物件的代理物件,以便向外界提供功能介面。其目的在於為其他物件提供一種代理以控制對這個物件的訪問。 這是因為一個物件的建立和初始化可能會產生很大的開銷,這也就意味著我們可以在真正需要這個物件時再對其進行相應的建立和初始化。 比如在檔案系統中對一個圖片的訪問,當我們以列表形式檢視檔案時,並不需要顯示整個圖片的資訊,只有在選中圖片的時候,才會顯示其預覽資訊,再在雙擊之後可能才會真正打個這個圖片,這時可能才需要從磁碟當中載入整個圖片資訊。 ![ProxyImage.png](https://img2020.cnblogs.com/other/1218435/202007/1218435-20200711143040925-1999685416.png) 對圖片代理的理解就如同上面的結構圖一樣,在檔案欄中預覽時,只是顯示代理物件當中的`fileName`等資訊,而代理物件當中的`image`資訊只會在真正需要Image物件的時候才會建立實線指向的聯絡。 通過上面的例子,可以清楚的看到代理模式在訪問物件時,引入了一定程度的間接性,這種間接性根據不同的情況可以附加相應的具體處理。 比如,對於遠端代理物件,可以隱藏一個物件不存在於不同地址空間的事實。對於虛代理物件,可以根據要求建立物件、增強物件功能等等。還有保護代理物件,可以為物件的訪問增加許可權控制。 這一系列的代理都體現了代理模式的高擴充套件性。但同時也會增加代理開銷,由於在客戶端和真實主題之間增加了代理物件,因此有些型別的代理模式可能會造成請求的處理速度變慢。並且實現代理模式需要額外的工作,有些代理模式的實現非常複雜。 對於上面的例子,可以用類圖更加詳細地闡述。 ![Proxy.png](https://img2020.cnblogs.com/other/1218435/202007/1218435-20200711143041151-1500506228.png) 在這樣一個結構中,jpg圖片與圖片代理類共同實現了一個圖片介面,並且在圖片代理類中存放了一個對於`JpgImage`的引用,這個引用在未有真正使用到時,是為null的,只有在需要使用時,才對其進行初始化。 ```java //Subject(代理的目標介面) public interface Image { public void show(); public String getInfo(); } //RealSubject(被代理的實體) public class JpgImage implements Image { private String imageInfo; @Override public void show() { //顯示完整圖片 ...... } @Override public String getInfo() { return imageInfo; } public Image loadImage(String fileName){ //從磁碟當中載入圖片資訊 // ...... return new JpgImage(); } } ``` ```java //Proxy(代理類) public class ImageProxy implements Image { private String fileName; private Image image; @Override public void show() { if (image==null){ image = loadImage(fileName); } image.show(); } @Override public String getInfo() { if (image==null){ return fileName; }else{ return image.getInfo(); } } public Image loadImage(String fileName){ //從磁碟當中載入圖片資訊 ...... return new JpgImage(); } } ``` ```java public class Client { public static void main(String[] args) { Image imageProxy = new ImageProxy(); imageProxy.getInfo(); imageProxy.show(); } } ``` 在實際的使用過程上,客戶就可以不再涉及具體的類,而是可以只關注代理類。 代理模式的種類有很多,根據代理的實現形式不同,可以劃分為: + 遠端代理:為一個物件在不同的地址空間提供區域性代表。 + 虛代理:為需要建立開銷很大的物件生成代理。(如上面的例項) + 保護代理:控制對原始物件的訪問。保護代理主要用於物件應該有不同的保護許可權時。 + 智慧指引:在訪問物件時執行一些附加的操作。 以上的代理都是靜態代理的形式,為什麼說是靜態呢,這是因為在實現的過程中,它的型別都是事先預定好的,比如`ImageProxy`這個類,它就只能代理`Image`的子類。 與靜態相對的自然就產生了動態代理。動態代理中,最主要的兩種方式就是基於JDK的動態代理和基於CGLIB的動態代理。這兩種動態代理也是Spring框架中實現AOP(Aspect Oriented Programming)的兩種動態代理方式。這裡,就不深入了,後面有機會再對動態代理做一個詳細的講解。 ## 中介者模式 - Mediator 中介者模式用一箇中介物件來封裝一系列的物件互動。中介者使各物件不需要顯示地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的互動。 中介者模式產生的一個重要原因就在於,面向物件設計鼓勵將行為分頁到各個物件中。而這種分佈就可能會導致物件間有許多連線,這些連線就是導致系統複用和修改困難的原因所在。 就比如一個機場排程的實現,在這個功能當中,各個航班就是Colleague,而塔臺就是Mediator;如果沒有塔臺的協調,那麼各個航班飛機的起降將只能由航班飛機之間形成一個多對多(一對多)的通訊網來控制,這種控制必然是及其複雜的;但是有了塔臺的加入,整個系統就簡化了許多,所有的航班只需要和塔臺進行通訊,也只需要接收來自塔臺的控制即可完成所有任務。這就使得多對多(一對多)的關係轉化成了一對一的關係。 ![mediator.png](https://img2020.cnblogs.com/other/1218435/202007/1218435-20200711143041541-1078007624.png) 看到中介者模式類圖的時候,有沒有發覺好像和哪個模式有點相似,有沒有點像觀察者模式。 之所以如此相似的原因就是觀察者模式和中介者模式都涉及到了物件狀態變化與狀態通知這兩個過程。觀察者模式當中,目標(Subject)的狀態發生變化就會通知其所有的(Observer);同樣,在中介者模式當中,其相應的同事類(一群通過中介者相互協作的類)狀態發生變化,就需要通知中介者,再由中介者來處理狀態資訊並反饋給其他的同事類。 因此,中介者模式的實現方法之一就是使用觀察者模式,將Mediator作為一個Observer,各個Colleague作為Subject,一旦Colleague狀態發生變化就傳送通知給Mediator。Mediator作出響應並將狀態改變的結果傳播給其他的Colleague。 另外還有一種方式,是在Mediator中定義一個特殊的介面,各個Colleague直接呼叫這個介面,並將自己作為引數傳入,然後由這個介面來選擇將資訊傳送給誰。 ```java //Mediator public class ControlTower { priv