前言 這一章主要講2個模式,一個是,適配器模式(負責將一個類的介面適配成用戶所期待的),另外一個是外觀模式(為子系統提供一個共同的對外介面),看完的第一反應是,為什麼要把它們兩放在同一章,難道它們有什麼不可告人的秘密? 難道是因為他們倆都很簡單嗎?不會不會,畢竟是大名鼎鼎的headfirst,怎麼可 ...
前言
這一章主要講2個模式,一個是,適配器模式(負責將一個類的介面適配成用戶所期待的),另外一個是外觀模式(為子系統提供一個共同的對外介面),看完的第一反應是,為什麼要把它們兩放在同一章,難道它們有什麼不可告人的秘密?
難道是因為他們倆都很簡單嗎?不會不會,畢竟是大名鼎鼎的headfirst,怎麼可能這麼草率,這我是萬萬不相信的!
細想了一下,我和工作的點點滴滴,我發現,一般到項目的後期,好像都比較容易用上這兩個東西...
當然,項目的後期並不是說一個項目自己從頭髮開到尾的項目,而是在它生命周期的後半段,比如適配器,用來適配老的介面,比如外觀模式,用來隱藏各個子系統,各個模塊的協作細節。
不過外觀模式卻不一定都是在後期才發力的:
1,前期如果系統比較複雜,在系統規劃的時候,就會有意識的對系統分層,為上層模塊提供一些高級的api。
2,在系統的中期呢,開發過程中,發現子系統越來越複雜,也可以提供類似的操作。
3,在系統後期,模塊越來越多,功能越來越複雜,還有歷史原因,外觀模式就更加有用了,畢竟,有一個簡單易用的API,比手動調用各個系統的邏輯那簡直是不要太舒服!
為什麼後期不重構?而是要做這些修修補補的工作呢?
舉個例子,房子上有棵樹,你覺得這棵樹很礙事,就把樹給幹掉了,因為你以為,是房子上面長的,結果呢?特麽是樹把房子吊著的!類似的坑實在是太多了,所以,重構需謹慎,且構且珍惜。
當然不是說重構不好,而是要綜合考量各方面的因素,而且,重構也用得上這些啊,畢竟,重構不是重寫...(誒,重寫好像也要用)
適配器模式
先看看鴨子介面(對應Target)
/** * 鴨子介面 */ public interface Duck { /** * 鴨叫 */ void quack(); /** * 飛行 */ void fly(); }
然後看一下火雞的介面和實現類(對應Adaptee)
/** * 火雞介面 */ public interface Turkey { /** * 火雞叫 */ void gobble(); /** * 飛行 */ void fly(); } /** * 野火雞 */ public class WildTurkey implements Turkey { public void gobble() { System.out.println("咯咯"); } public void fly() { System.out.println("我在飛,雖然我飛的很近"); } }
首先可以看出,它們的之間有一些共同之處,都有叫聲,都可以飛行,這個也是適配的前提,有共同點!
如果沒有共同點,是不是去隔壁的其他設計模式看看?
OK,接下來開始適配操作
火雞適配器(Adapter)
/** * 火雞適配器 */ public class TurkeyAdapter implements Duck { Turkey turkey;//持有一個火雞對象 public TurkeyAdapter(Turkey turkey) { this.turkey = turkey; } /** * 鴨叫 */ public void quack() { turkey.gobble(); } /** * 飛行 */ public void fly() { //適配的時候,這裡模擬飛行5次 for(int i= 0; i < 5; i++) { turkey.fly(); } } }
適配器的邏輯也很簡單
首先,實現Duck介面,要讓Client能夠調用,那麼首先得長得和別人一樣啊
其次,持有一個真正的處理對象,然後再根據Duck介面來進行適配,比如這裡,quack介面,就直接調用Turkey#gobble(),而fly()可能是因為某種神秘力量,需要火雞飛行的距離和鴨子一樣遠,所以需要手動去適配,在這裡添加了適配的代碼
最後,適配器的作用就是把一個類轉換成另外一個類,轉換的時候可能需要一些邏輯上的處理,讓它能符合用戶的期待
public class DuckClient { public static void main(String[] args) { //初始化一隻火雞 WildTurkey turkey = new WildTurkey(); //偽裝成一隻鴨子 Duck duck = new TurkeyAdapter(turkey); System.out.println("鳴叫:"); duck.quack(); System.out.println("------------------"); System.out.println("飛行:"); duck.fly(); } }
結果:
外觀模式
核心思想:為子系統們提供一套通用的對外介面(高級API)
為什麼會有這樣的需求呢?
各個子系統在設計過程中,或者在實際使用的過程中會發現,有一些通用的步驟,對於更加高的調用層來說,它們其實不需要知道底層是通過哪些步驟來實現的,更多的是,以一個統一的介面來調用。
比如,在想在家裡搞一個家庭影院,需要以下步驟:
1,燈光不能太亮,亮度需要調低到10
2,需要打開投影機,並且要調整到寬屏模式
3,音響需要調整成環繞立體音,音量設置成5
4,打開DVD開始播放
代碼如下:
燈光:
/** * 影院燈光 */ public class TheaterLights { String description; public TheaterLights(String description) { this.description = description; } public void on() { System.out.println(description + " 打開"); } public void off() { System.out.println(description + " 關閉"); } public void dim(int level) { System.out.println(description + " 亮度調節到:" + level + "%"); } public String toString() { return description; } }View Code
投影儀:
/** * 投影儀屏幕 */ public class Screen { String description; public Screen(String description) { this.description = description; } public void up() { System.out.println(description + " 上升"); } public void down() { System.out.println(description + " 下降"); } public String toString() { return description; } } /** * 投影儀 */ public class Projector { String description; DvdPlayer dvdPlayer; public Projector(String description, DvdPlayer dvdPlayer) { this.description = description; this.dvdPlayer = dvdPlayer; } public void on() { System.out.println(description + " 打開"); } public void off() { System.out.println(description + " 關閉"); } public void wideScreenMode() { System.out.println(description + " 調整到寬屏模式"); } public void tvMode() { System.out.println(description + " 調整到tv模式"); } public String toString() { return description; } }View Code
音響:
/** * 音響 */ public class Amplifier { String description; public Amplifier(String description) { this.description = description; } public void on() { System.out.println(description + " 打開"); } public void off() { System.out.println(description + " 關閉"); } //立體聲 public void setStereoSound() { System.out.println(description + " 立體聲模式"); } //環繞聲 public void setSurroundSound() { System.out.println(description + " 環繞聲模式"); } public void setVolume(int level) { System.out.println(description + " 調整音量到: " + level); } public String toString() { return description; } }View Code
DVD播放器:
/** * DVD播放器 */ public class DvdPlayer { String description; int currentTrack; Amplifier amplifier; String movie; public DvdPlayer(String description, Amplifier amplifier) { this.description = description; this.amplifier = amplifier; } public void on() { System.out.println(description + " 播放"); } public void off() { System.out.println(description + " 關閉"); } public void play(String movie) { this.movie = movie; currentTrack = 0; System.out.println(description + " 播放 \"" + movie + "\""); } public String toString() { return description; } }View Code
不重要的代碼就摺疊了,免得難得看,不使用外觀模式,需要調用一堆代碼:
/** * 不使用外觀模式 */ public class Client { public static void main(String[] args) { Amplifier amp = new Amplifier("Top-O-Line 揚聲器"); DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD播放器", amp); Projector projector = new Projector("Top-O-Line 投影儀", dvd); TheaterLights lights = new TheaterLights("客廳燈"); Screen screen = new Screen("投影儀銀幕"); System.out.println("準備看電影..."); lights.dim(10); screen.down(); projector.on(); projector.wideScreenMode(); amp.on(); amp.setSurroundSound(); amp.setVolume(5); dvd.on(); dvd.play("奪寶奇兵"); } }
使用外觀模式,一行解決:
/** * 使用外觀模式後的測試類 */ public class FacadeClient { private static HomeTheaterFacade HOME_THEATER; static{ Amplifier amp = new Amplifier("Top-O-Line 揚聲器"); DvdPlayer dvd = new DvdPlayer("Top-O-Line DVD播放器", amp); Projector projector = new Projector("Top-O-Line 投影儀", dvd); TheaterLights lights = new TheaterLights("客廳燈"); Screen screen = new Screen("投影儀銀幕"); HOME_THEATER = new HomeTheaterFacade(amp, dvd, projector, screen, lights); } public static void main(String[] args) { //看電影 HOME_THEATER.watchMovie("奪寶奇兵"); } }
我擦?咋還是這麼多行?
static塊裡面的代碼是初始化代碼,一般使用spring,都是依賴註入的東西,其實調用就一行:
HOME_THEATER.watchMovie("奪寶奇兵");
但是能夠一鍵解決的,更多的是一些通用的操作,比如說,例子中,燈光不能太亮,你想把它調到5,不想用預設的10,,那麼可能就只能自己寫一遍外觀模式封裝的邏輯了。
那麼這裡就有個問題了,能不能重載方法,讓它支持可以自定義燈光亮度這個參數呢?對於這個我只能說,要看業務需求了,如果100個人裡面只有1個人用,那麼對於系統產生的複雜度可能比 產生的價值高,反過來,可能就需要去實現。
但是,如果這種需求越來越多,系統變得越來越複雜,那外觀模式還是一個簡單可愛的小姐姐嗎?如果不實現,就無法達到隱藏子系統複雜度的痛點,如果實現,就會產生新的API調用的複雜度,我終於知道為啥我特麽還在學習設計模式了...
說了這麼多,說說它的優缺點吧
優點:
1,對客戶屏蔽了子系統組件使用起來門檻更低。
2,實現了子系統與客戶之間的松耦合關係。
3,雖然提供了訪問子系統的統一入口,但是並不影響用戶直接使用子系統類。
缺點:
1,通過外觀類訪問子系統時,減少了可變性和靈活性。
2,在新的子系統加入,或者子系統介面變更時,可能需要修改外觀類或客戶端的源代碼,違背了“開閉原則”。