封裝變化之介面隔離
在組件的構建程序當中,某些介面之間直接的依賴常常會帶來很多問題、甚至根本無法實作,采用添加一層間接(穩定)的介面,來隔離本來互相緊密關聯的介面是一種常見的解決方案,
這里的介面隔離不同于介面隔離原則,介面隔離原則是對介面職責隔離,也就是盡量減少介面職責,使得一個類對另一個類的依賴應該建立在最小的介面上,
而這里所講到的介面隔離是對依賴或者通信關系的隔離,通過在原有系統中加入一個層次,使得整個系統的依賴關系大大的降低,而這樣的模式主要有外觀模式、代理模式、中介者模式和配接器模式,
外觀模式 - Facade
Facade模式其主要目的在于為子系統中的一組介面提供一個一致的界面(介面),Facade模式定義了一個高層介面,這個介面使得更加容易使用,
在我們對系統進行研究的時候,往往會采用抽象與分解的思路去簡化系統的復雜度,因此在這個程序當中就將一個復雜的系統劃分成為若干個子系統,也正是因為如此,子系統之間的通信與相互依賴也就增加了,為了使得這種依賴達到最小,Facade模式正好可以解決這種問題,
Facade模式體現的更多的是一種介面隔離的思想,它體現在很多方面上,最常見的比如說用戶圖形界面、作業系統等,這都可以體現這樣一個思想,

Facade模式從結構上可以簡化為上面這樣一種形式,但其形式并不固定,尤其是體現在其內部子系統的關系上,因為其內部的子系統關系肯定是復雜多樣的,并且SubSystem不一定是類或者物件,也有可能是一個模塊,這里只是用類圖來表現Facade模式與其子系統之間的關系,
從代碼體現上來看,可以這樣表現:
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來對外隔離它的系統內部復雜依賴呢?看下面:
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模式的實作帶來很大的影響,
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模式中,我們創建具有現有物件的代理物件,以便向外界提供功能介面,其目的在于為其他物件提供一種代理以控制對這個物件的訪問,
這是因為一個物件的創建和初始化可能會產生很大的開銷,這也就意味著我們可以在真正需要這個物件時再對其進行相應的創建和初始化,
比如在檔案系統中對一個圖片的訪問,當我們以串列形式查看檔案時,并不需要顯示整個圖片的資訊,只有在選中圖片的時候,才會顯示其預覽資訊,再在雙擊之后可能才會真正打個這個圖片,這時可能才需要從磁盤當中加載整個圖片資訊,

對圖片代理的理解就如同上面的結構圖一樣,在檔案欄中預覽時,只是顯示代理物件當中的fileName等資訊,而代理物件當中的image資訊只會在真正需要Image物件的時候才會建立實線指向的聯系,
通過上面的例子,可以清楚的看到代理模式在訪問物件時,引入了一定程度的間接性,這種間接性根據不同的情況可以附加相應的具體處理,
比如,對于遠程代理物件,可以隱藏一個物件不存在于不同地址空間的事實,對于虛代理物件,可以根據要求創建物件、增強物件功能等等,還有保護代理物件,可以為物件的訪問增加權限控制,
這一系列的代理都體現了代理模式的高擴展性,但同時也會增加代理開銷,由于在客戶端和真實主題之間增加了代理物件,因此有些型別的代理模式可能會造成請求的處理速度變慢,并且實作代理模式需要額外的作業,有些代理模式的實作非常復雜,
對于上面的例子,可以用類圖更加詳細地闡述,

在這樣一個結構中,jpg圖片與圖片代理類共同實作了一個圖片介面,并且在圖片代理類中存放了一個對于JpgImage的參考,這個參考在未有真正使用到時,是為null的,只有在需要使用時,才對其進行初始化,
//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();
}
}
//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();
}
}
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;如果沒有塔臺的協調,那么各個航班飛機的起降將只能由航班飛機之間形成一個多對多(一對多)的通信網來控制,這種控制必然是及其復雜的;但是有了塔臺的加入,整個系統就簡化了許多,所有的航班只需要和塔臺進行通信,也只需要接收來自塔臺的控制即可完成所有任務,這就使得多對多(一對多)的關系轉化成了一對一的關系,

看到中介者模式類圖的時候,有沒有發覺好像和哪個模式有點相似,有沒有點像觀察者模式,
之所以如此相似的原因就是觀察者模式和中介者模式都涉及到了物件狀態變化與狀態通知這兩個程序,觀察者模式當中,目標(Subject)的狀態發生變化就會通知其所有的(Observer);同樣,在中介者模式當中,其相應的同事類(一群通過中介者相互協作的類)狀態發生變化,就需要通知中介者,再由中介者來處理狀態資訊并反饋給其他的同事類,
因此,中介者模式的實作方法之一就是使用觀察者模式,將Mediator作為一個Observer,各個Colleague作為Subject,一旦Colleague狀態發生變化就發送通知給Mediator,Mediator作出回應并將狀態改變的結果傳播給其他的Colleague,
另外還有一種方式,是在Mediator中定義一個特殊的介面,各個Colleague直接呼叫這個介面,并將自己作為引數傳入,然后由這個介面來選擇將資訊發送給誰,
//Mediator
public class ControlTower {
private List<Flight> flights
= new ArrayList<>();
public void addFlight(Flight flight){
flights.add(flight);
}
public void removeFlight(Flight flight){
flights.remove(flight);
}
public void control(Flight flight){
//對航班進行起降控制
......
//如果航班起飛,則從flights移除
//如果航班降落,則加入到flights
}
}
public class Flight {
private ControlTower cTower;
public void setcTower(ControlTower cTower) {
this.cTower = cTower;
}
public void changed(){
cTower.control(this);
}
}
public class Flight1 extends Flight{
public void takeOff(){
//起飛操作
......
}
public void land(){
//降落操作
......
}
}
public class Flight2 extends Flight{
//起飛 降落 操作
......
}
public class Flight3 extends Flight{
//同樣 起飛 降落 操作
......
}
那么客戶怎樣使用這樣一個模式呢?看下面這樣一個操作:
public class Client {
public static void main(String[] args) {
ControlTower controlTower = new ControlTower();
//假設一個飛機入場要么是有跑道空閑要么是另一個飛機起飛
Flight f1 = new Flight1();
f1.setcTower(controlTower);
//此時一號機降落,
//controlTower呼叫contorl控制飛機起降
f1.changed();
Flight f2 = new Flight2();
f2.setcTower(controlTower);
//此時二號機降落,
//controlTower呼叫contorl控制1號飛機起飛,二號降落
f2.changed();
.......
}
}
中介者模式主要解決的是,如果系統中物件之間存在比較復雜的參考關系,導致它們之間的依賴關系結構混亂而且難以復用該物件,就可以使用中介者來簡化依賴關系,但是這也可能會使得中介者會龐大,變得復雜難以維護,所以在使用中介者模式時,盡量是在保持中介者穩定的情況下使用,
配接器模式 - Adapter
配接器的目的在于將一個類的介面轉換成客戶希望的另外一個介面,從而使得原本由于介面不兼容而不能在一起作業的類可以在一起作業,
首先在使用配接器的時候,需要明確的是,配接器不是在詳細設計時添加的,而是解決正在服役的專案的問題,為什么,因為配接器本身就存在一些問題,比如明明我想呼叫的是一個檔案介面,結果傳輸出來的卻是一張圖片,如果系統當中出現太多這樣的情況,那無異會使得系統的應用變得極其困難,
所以只有在系統正在運用,并且重構困難的情況下,才選擇使用配接器來適配介面,
而配接器模式又根據作用物件可以分為類配接器和物件配接器兩種實作方式,
假設我們現在已經存在一個播放器,這個播放器只能播放mp3格式的音頻,但是現在又出現了一個新的播放器,這個播放器有兩種播放格式mp4和wma,
也就是說,現在的情況可以用下圖來進行描述:

這時候,為了右邊的系統Player 融入到右邊中,就可以采用配接器模式,

通過增加一個配接器,并將player作為配接器的一個屬性,當傳入具體的播放器時,就在newPlay()中呼叫player.play(),
具體實作如下:
//Adaptee (適配者,要求將這個存在的介面適配成目標的介面)
public interface Player {
public void play();
}
public class Mp3Player implements Player {
@Override
public void play() {
System.out.println("播放mp3格式");
}
}
//Target(適配目標,需要適配成那個目標的介面)
public interface NewPlayer {
public void newPlay();
}
public class WmaNewPlayer implements NewPlayer {
@Override
public void newPlay() {
System.out.println("播放wmas格式");
}
}
public class Mp4NewPlayer implements NewPlayer {
@Override
public void newPlay() {
System.out.println("播放mp4格式");
}
}
接下來就是配接器的實作了,
//物件配接器
//首先在配接器中,增加一個適配者(Player)的參考
//然后使用適配者(Player)實作適配目標(NewPlayer)的介面
public class PlayerAdapter implements NewPlayer {
private Player player;
public PlayerAdapter(Player player){
this.player = player;
}
@Override
public void newPlay() {
player.play();
}
}
然后整個系統的呼叫變化為:
public class Client {
public static void main(String[] args) {
//播放mp4,wma的形式不變
NewPlayer mp4Player = new Mp4NewPlayer();
mp4Player.newPlay();
NewPlayer wmaPlayer = new WmaNewPlayer();
wmaPlayer.newPlay();
//如果要播放mp3格式,可以使用配接器來進行
Player adapter
= new PlayerAdapter(new Mp3Player());
adapter.newPlay();
}
}
這樣的一個適配程序可能存在一點不完善的地方,就在于,雖然對兩都進行了適配,但呼叫方式不統一,為了統一呼叫程序,其實還可以做如下修改:
//物件配接器修改為
//首先在配接器中,增加適配者(newPlayer)和目標(player)的參考
//然后使用適配者(newPlayer)實作適配目標(Player)的介面
public class PlayerAdapter implements NewPlayer {
private NewPlayer newPlayer;
private Player player;
public PlayerAdapter(NewPlayer newPlayer){
this.newPlayer = newPlayer;
}
public PlayerAdapter(Player layer){
this.player = player;
}
@Override
public void newPlay() {
if(player!=null){
player.play();
}else{
newPlayer.newPlay();
}
}
}
//這樣修改配接器之后,客戶類的呼叫就變成了都通過配接器來進行
public class Client {
public static void main(String[] args) {
//播放mp3
Player adapter1
= new PlayerAdapter(new Mp3Player());
adapter1.newPlay();
//播放mp4
Player adapter2
= new PlayerAdapter(new Mp4NewPlayer());
adapter2.newPlay();
//播放wma
Player adapter3
= new PlayerAdapter(new WmaNewPlayer());
adapter3.newPlay();
}
}
之前說了除了物件配接器之外,還有類配接器,而類配接器如果要實作就需要適配中的適配者是一個已經實作的結構,如果沒有實作還需要適配者自己實作,這種實作方式就導致其靈活性沒有物件配接器那么高,
其類圖就是上面這樣一種形式,主要區別體現在配接器的實作,而其部分變化不大,
//類配接器
public class PlayerAdapter extends Mp3Player implements NewPlayer {
@Override
public void newPlay() {
play();
}
}
//客戶呼叫程序就變化為:
public class Client {
public static void main(String[] args) {
//播放mp4,wma的形式不變
NewPlayer mp4Player = new Mp4NewPlayer();
mp4Player.newPlay();
NewPlayer wmaPlayer = new WmaNewPlayer();
wmaPlayer.newPlay();
//如果要播放mp3格式,可以使用配接器來進行
Player adapter = new PlayerAdapter();
adapter.newPlay();
}
}
但是如果Player存在不同子類,那明顯使用物件配接器是更好的選擇,
當然也不是說類配接器就不一定沒有物件配接器之外的優勢,兩者的使用有不同的權衡,
類配接器:
- 用一個具體的Adapater類對Adaptee和Target進行匹配,結果是當我們想要匹配一個類以及所有它的子類時,類配接器就不再適用,
- 因為Adapter是Adaptee的子類,這就使得Adapter可以重定義Adaptee的部分行為,
- 不需要再引入物件,不需要引入額外的參考就可以得到adaptee,
物件配接器:
- 允許一個Adapter與多個Adaptee——即Adaptee本身以及它的所有子類同時作業,并且Adapter也可以一次給所有的Adaptee添加功能,
- 想要重定義Adaptee的行為比較困難,但對于增強Adaptee的功能卻很容易,如果要自定義Adaptee的行為,就只能生成Adaptee的子類來實作重定義,
公眾號:良許Linux
有識訓?希望老鐵們來個三連擊,給更多的人看到這篇文章
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/38447.html
標籤:Linux
上一篇:007.Nginx虛擬主機
