《Head First设计模式》第七章-适配器模式、外观模式
適配器模式
適配器模式是什么,你一定不難理解,因為現實中到處都是。比如說:
如果你需要在歐洲國家使用美國制造的筆記本電腦,你可能需要使用一個交流電的適配器……
當你不想改變現有的代碼,解決接口不適配問題,便可使用適配器模式,你可以寫一個類,將新廠商接口轉接成你所期望的接口。
定義適配器模式:將一個類的接口,轉換成客戶期望的另一個接口。適配器讓原本接口不兼容的類可以合作無間。
現在我們已經知道什么是適配器了,讓我們后退一步,再次看看各部分之間的關系。
類圖:
讓我們以開頭所提到的電腦電源插頭適配的問題為例:
新建一個電腦類:
| 1 2 3 4 5 6 7 8 9 10 11 | public class Computer { ?????//充電方法只能使用兩孔插座,只能傳入兩孔插座做參數 ????public void charge(Socket_Two socket_Two) { ????????socket_Two.connect(); ????????addPower();//調用增加電量的方法 ????} ????//充電成功,電量增加 ????private void addPower() { ????????System.out.println("電源已連接,充電中..."); ????} } |
創建一個兩孔插座接口:
| 1 2 3 | public interface Socket_Two { ????void connect();//連接插座方法 } |
再創建一個三孔插座類:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class Socket_Three {??? ????//連接方法 ????public void connect() { ????????//調用每個孔接通的方法 ????????leftConnect(); ????????rightconnect(); ????????extraConner(); ????} ????//三孔接通方法 ????public void rightconnect() { ????????System.out.println("火線接通..."); ????} ????public void leftConnect() { ????????System.out.println("零線接通..."); ????} ????public void extraConner() { ????????System.out.println("地線接通..."); ????} } |
創建一個適配器:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | //三孔插座與兩孔插座適配器,直接關聯被適配類,同時實現標準接口 public class LineWithSocket_Two?implements Socket_Two { ????// 直接關聯被適配類 ????private Socket_Three socket_Three; ????//通過構造函數傳入具體需要適配的被適配類對象 ????public LineWithSocket_Two(Socket_Three socket_Three) { ????????this.socket_Three=socket_Three; ????} ????@Override ????public void connect() { ????????//使用委托的方式完成特殊功能 ????????System.out.println("我是適配器:通過我可以讓兩腳插頭使用三孔插座") ????????socket_Three.leftConnect(); ????????socket_Three.rightconnect(); ????} } |
測試類
| 1 2 3 4 5 6 7 8 | public static void main(String[] args) { ????Computer computer=new Computer(); ????Socket_Three socket_three=new Socket_Three(); ????//調用適配器類來完成適配 ????LineWithSocket_Two lineWithScoket_Two=new LineWithSocket_Two(socket_three); ????System.out.println("使用適配器:"); ????computer.charge(lineWithScoket_Two); } |
運行結果:
其他
實際上適配器模式中有“兩種”適配器:“對象”適配器和“類”適配器。
究竟什么是“類”適配器?為什么我們還沒告訴你這種適配器?因為你需要多重繼承才能夠實現它,這在Java中是不可能的。但是當你在使用多重繼承語言的時候,還是可能遇到這樣的需求。
讓我們看看多重繼承的類圖:
看起來很熟悉嗎?沒錯,唯一的差別就在于適配器繼承了Target和Adaptee。而對象適配器利用組合的方式將請求傳送給被適配者。
對象適配器和類適配器使用兩種不同的適配方法(分別是組合與繼承)。
適配器模式適用場景
適配器模式優缺點
優點:
類適配器優點:
- 由于適配器類是適配者類的子類,因此可以在適配器類中置換一些適配者的方法,使得適配器的靈活性更強。
對象適配器優點:
- 把多個不同的適配者適配到同一個目標,也就是說,同一個適配器可以把適配者類和他的子類都適配到目標接口。
缺點:
- 實現適配器所需要的工作和目標接口的大小成正比,接口越復雜適配器也越復雜。
類適配器缺點:
- 對于Java、C#等不支持多重繼承的語言,一次最多只能適配一個適配者類,而且目標抽象類只能為接口,不能為類,其使用有一定的局限性,不能將一個適配者類和他的子類同時適配到目標接口。
對象適配器的缺點:
- 與類適配器模式相比,要想置換適配者類的方法就不容易。
?
通過上一篇你已經知道適配器模式是如何將一個類的接口轉換成另一個符合客戶期望的接口的。你也知道在Java中要做到這一點,必須將一個不兼容接口的對象包裝起來,變成兼容的對象。
? 我們現在要看一個改變接口的新模式,但是它改變接口的原因是為了簡化接口,這個模式被巧妙的命名為外觀模式。它將一個或數個類的復雜的一切都隱藏在背后,只顯露出一個干凈美好的外觀。
定義
外觀模式定義:外觀模式提供了一個統一的接口,用來訪問子系統中的一群接口。外觀定義了一個高層接口,讓子系統更容易使用。
外觀模式實現了最少知識原則(Least Knowledge principle),這個原則希望不要讓太多的類耦合在一起,對用戶來說只和一個外觀類打交道了,達到客戶和一群子系統的解耦。
例題:搭建一個家庭影院系統
系統內包含設備:DVD播放器、投影機、自動屏幕、立體聲音響、爆米花機........
來看看這些組件的組成:
當你把所有設備布置好后,準備看電源時...你忘了你必須要先一個個啟用這些設備...關閉時也還將要進行一遍反向操作(崩潰....)。
結果你發現要使用你的家庭影院是那么的麻煩!
因此,我們引入外觀模式,有了外觀模式,通過實現一個更合理的接口的外觀類,你可以將一個復雜的子系統變的容易使用。看看改變之后的類圖:
定義這些媒體類:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 | class Amplifier{ ????public void on(){ ????????System.out.println("歡迎使用功放類。。"); ????} ????public void off(){ ????????System.out.println("已經關閉功放。。"); ????} ????public void setCD(){ ????????System.out.println("正在安放CD。。。"); ????} ????public void setDVD(){ ????????System.out.println("正在安放DVD、。。"); ????} ????public void setStereoSound(){ ????????System.out.println("設置立體聲。。"); ????} ????public void setSurroundSound(){ ????????System.out.println("設置環繞立體聲。。"); ????} ????public void setTime(){ ????????System.out.println("正在設置時間。。"); ????} ????public void setVolume(){ ????????System.out.println("正在設置音量。。"); ????} } //定義Tuner類 class Tuner{ ????public void on(){ ????????System.out.println("正在 打開調諧器。。"); ????} ????public void off(){ ????????System.out.println("正在關閉調諧器。。"); ????} ????public void setAM(){ ????????System.out.println("正在設置am。。"); ????} ????public void setFM(){ ????????System.out.println("正在設置頻道。。"); ????} ????public void setFrequency(){ ????????System.out.println("正咋設置頻道。。"); ????} } //定義DVD播放器類 class DVDPlayer{ ????public void on(){ ????????System.out.println("正在打開DVD。。"); ????} ????public void off(){ ????????System.out.println("正在關閉DVD。。"); ????} ????public void pause(){ ????????System.out.println("已經暫停DVD播放。。"); ????} ????public void play(){ ????????System.out.println("正在播放DVD。。"); ????} ????public void setTwoChannelAudio(){ ????????System.out.println("正在設置雙頻道。。"); ????} ????public void setSurroundAudio(){ ????????System.out.println("正在設置環繞立體聲。。"); ????} } //定義CD播放器 class CDPlayer{ ????public void on(){ ????????System.out.println("正在打開CD"); ????} ????public void off(){ ????????System.out.println("正在關閉CD"); ????} ????public void eject(){ ????????System.out.println("彈出CD播放器!"); ????} ????public void pause(){???? ????} ????public void play(){ ????} ????public String toString(){ ????????return "hello panda"; ????} } //定義投影儀 class Projector{ ????public void on(){ ????????System.out.println("正在打開投影儀。。"); ????} ????public void off(){ ????????System.out.println("正在關閉投影儀。。"); ????} ????public void setTVMode(){ ????????System.out.println("正在設置tv模式。。"); ????} ????public void setWideScreenMode(){ ????????System.out.println("正在設置寬屏模式。。"); ????} } //定義屏幕 class Screen{ ????public void up(){ ????????System.out.println("正在生起屏幕。。"); ????} ????public void down(){ ????????System.out.println("正在放下屏幕。。"); ????} } //定義爆米花機 class PopcornPopper{ ????public void on(){ ????????System.out.println("正在打開爆米花機。。"); ????} ????public void off(){ ????????System.out.println("正在關閉爆米花機。。"); ????} ????public? void pop(){ ????????System.out.println("正在蹦爆米花。。"); ????} } //定義影院燈光 class TheaterLights{ ????public void on(){ ????????System.out.println("正在打開燈光。。"); ????} ????public void off(){ ????????System.out.println("正在關閉燈光。。"); ????} ????public void dim(){ ????????System.out.println("正在調暗燈光。。"); ????} ? } |
多媒體的具體類準備好就可以定義外觀模式類了。
外觀模式類:倆方法,一個看電影-打開一系列設備,一個電影結束-關閉一系列設備
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | //定義外觀模式家庭影院 class HomeTheaterFacade{ ????Amplifier amp; ????Tuner tuner ; ????DVDPlayer dvd; ????CDPlayer cd; ????Projector project; ????TheaterLights light; ????Screen screen; ????PopcornPopper pop; ????//構造的時候拿到這些對象 ????public HomeTheaterFacade(Amplifier amp,Tuner tuner,DVDPlayer dvd,? ????????????CDPlayer cd ,Projector project,TheaterLights light,Screen screen ????????????,PopcornPopper pop){ ????????this.amp = amp; ????????this.tuner = tuner; ????????this.dvd = dvd; ????????this.cd = cd; ????????this.project = project; ????????this.light? = light; ????????this.screen = screen; ????????this.pop = pop; ????} ????//看電影? 放一個方法里來執行一系列動作 ????public void watchMovie(String movie){ ????????System.out.println("get ready to watch a movie.."); ????????pop.on();//首先打開爆米花機 ????????pop.pop();//然后蹦爆米花 ????????light.dim();//把燈光調暗 ????????screen.down();//投影儀放下來 ????????project.on(); ????????project.setWideScreenMode(); ????????amp.on(); ????????amp.setDVD(); ????????amp.setSurroundSound(); ????????amp.setVolume(); ????????dvd.on(); ????????dvd.play(); ????} ????//電影結束 ????public void endMovie(String movie){ ????????System.out.println("shutting movie theater down.."); ????????pop.off(); ????????light.on(); ????????screen.up(); ????????project.off(); ????????amp.off(); ???????dvd.off(); ????} } |
測試
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class FacadePattern { ????public static void main(String args[]){ ????????Amplifier amp =?new Amplifier(); ????????Tuner tuner =?new Tuner() ; ????????DVDPlayer dvd =?new DVDPlayer(); ????????CDPlayer cd =?new CDPlayer(); ????????Projector project =?new Projector();; ????????TheaterLights light =?new TheaterLights(); ????????Screen screen =?new Screen(); ????????PopcornPopper pop =?new PopcornPopper();?????????????????? ????????HomeTheaterFacade facade =?new HomeTheaterFacade(amp,tuner,dvd,cd,project,light,screen,pop); ????????facade.watchMovie("movie"); ????????facade.endMovie("movie"); ????} ? } |
通過上面的代碼可以看出,每個類對象都要執行一些方法,如果直接new這些類創建對象去調方法會與這些類產生耦合,這時單獨再寫一個外觀類,構造初始化時拿到這些類對象,在一個方法里去調這些類對象的方法,這樣對客戶來說只和一個類打交道,與子系統的一堆類解耦了。此類原則即為:最少知識原則
最少知識原則:只和你的密友談話。
客戶端只和外觀談話,不和子設備,如DVD播放機、投影儀等談話,降低了客戶端和設備的耦合度。
下面給出最少知識原則的指導思想:
就任何對象而言,在該對象的方法內,我們只應該調用屬于以下范圍的方法:
1.該對象本身
2.被當做方法的參數而傳過來的對象
3.該方法所創建或實例化的任何對象
4.對象的任何組件
盡可能自己封裝子設備的方法,以便減少客戶端和子設備的耦合。
外觀模式優點
松耦合:使客戶端與子系統之間解耦,讓子系統內部的模塊功能更容易擴展與維護。
簡單易用:客戶端無需了解子系統的內部實現及內部構成,只需要與外觀類交互即可。
更好地劃分訪問層次:部分方法對外,部分方法對內交互使用。子系統將暴露在外的功能集中到外觀類中可以隱藏子系統內部細節。
適配器、裝飾者、外觀模式對比
- 在介紹裝飾者模式時,引出了一個開閉原則,即對修改關閉,對擴展開放。裝飾者模式主要強調的是在不改變原有類的基礎上,添加新功能。
- 適配器模式主要是對適配對象進行調整,以便適合消費者的需求。從而達到消費者和被適配者解耦的目的。
- 外觀模式的特點主要是簡化接口,以及減少客戶端對外觀組件的耦合。因為如果客戶端變化來,組件的子系統變化了,不用影響客戶端。除此之外,在封裝組件時,適當的在外觀類中添加一些自己想要的規則。如上面例子中各設備的開關順序
總結
- 當需要使用一個現有的類,而其接口并不符合你的需要時,就使用適配器。
- 當需要簡化并統一一個很大的接口或者一群復雜的接口時,使用外觀。
- 適配器改變接口以符合客戶的希望
- 外觀將一個客戶從復雜的子系統中解耦。
- 實現一個適配器可能需要一番功夫,也可能不費工夫,視目標接口的大小與復雜度而定。
- 實現一個外觀,需要將子系統組合進外觀中,然后將工作委托給子系統執行。
- 適配器模式有兩種形式,對象適配器和類適配器。類適配器需要用到多重繼承。
- 你可以為一個子系統實現一個以上的外觀。
- 適配器將一個對象包裝起來以改變其接口;裝飾器將一個對象包裝起來以增加新的行為和責任,而外觀將一群對象包裝起來以簡化其接口。
總結
以上是生活随笔為你收集整理的《Head First设计模式》第七章-适配器模式、外观模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: leetcode516 最长回文子序列
- 下一篇: 《Head First设计模式》第九章(