適配器模式
場景描述:想象一下我們維護了一套老系統,並於一個廠商通過接口進行交互。但是最近廠商更新它們的代碼,並變更了接口格式,我們如何在不修改老系統代碼的基礎上,優雅的過渡呢?
提出方案:我們可以想象一下我們平時使用的轉換頭,這就是一種適配器。我們可以通過這種方式來優雅的“將方形放入圓形中”。
我們嘗試舉一個例子,假設現在有一個鴨子接口
public interface Duck {
public void quack();
public void fly();
}
並作出一個實現
public class MallardDuck implements Duck {
@Override
public void quack() {
System.out.println("Quack");
}
@Override
public void fly() {
System.out.println("I'm flying");
}
}
接着我們有另一個火雞接口
public interface Turkey {
public void gobble();
public void fly();
}
以及一個實現
public class WildTurkey implements Turkey {
@Override
public void gobble() {
System.out.println("Gobble gobble");
}
@Override
public void fly() {
System.out.println("I'm flying a short distance");
}
}
我們如何能夠將火雞作爲鴨子呢?可以提供一個適配器來做接口的轉換
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey){
this.turkey = turkey;
}
@Override
public void quack() {
turkey.gobble();
}
@Override
public void fly() {
for (int i = 0; i < 5; i++){
turkey.fly();
}
}
}
然後我們測試一下
public class DuckTestDrive {
public static void main(String[] args) {
MallardDuck duck = new MallardDuck();
WildTurkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);
System.out.println("The Turkey says...");
turkey.gobble();
turkey.fly();
System.out.println("\nThe Duck says...");
testDuck(duck);
System.out.println("\nThe TurkeyAdapter says...");
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck){
duck.quack();
duck.fly();
}
}
可以通過結果看到我們現在將這個火雞作爲一隻鴨子的實現了。
適配器模式將一個類的接口,轉換成客戶期望的另一個接口。適配器讓原本接口不兼容的類可以合作無間。
我們看一下它的類圖
實現一個適配器所需要進行的工作,和目標接口的大小成正比。
由於Java不支持多繼承,所以適配器又分爲“對象”適配器(Java)和“類”適配器(支持多繼承的其他語言)。
再看一下“類”適配器的類圖
外觀模式
場景描述:我們現在有一個家庭影院,其中包含了DVD,投影機,自動屏幕,立體環繞聲等等,每當我們想要看電影的時候,都需要進行一系列操作;而看完電影時,又需要反向再操作一遍。
改進方案:我們嘗試進行一種近似流水線的改造,通過將一系列操作變成一個更加高層的操作。
如下是我們的改進方案的代碼
public class HomeTheaterFacade {
Amplifier amp;
Tuner tuner;
DvdPlayer dvd;
CdPlayer cd;
Projector projector;
TheaterLights lights;
Screen screen;
PopcornPopper popper;
public HomeTheaterFacade(Amplifier amp, Tuner tuner, DvdPlayer dvd, CdPlayer cd, Projector projector, TheaterLights lights, Screen screen, PopcornPopper popper){
this.amp = amp;
this.tuner = tuner;
this.dvd = dvd;
this.cd = cd;
this.projector = projector;
this.lights = lights;
this.screen = screen;
this.popper = popper;
}
public void watchMovie(String movie){
System.out.println("Get ready to watch a movie...");
popper.on();
popper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.wideScreenMode();
amp.on();
amp.setDvd();
amp.setSurroundSound();
amp.setVolume(5);
dvd.on();
dvd.play(movie);
}
public void endMovie(){
System.out.println("Shutting movie theater down...");
popper.off();
lights.on();
screen.up();
projector.off();
amp.off();
dvd.stop();
dvd.eject();
dvd.off();
}
這樣我們測試一下,只可以通過一步操作就完成之前的一系列操作
public class HomeTheaterTestDrive {
public static void main(String[] args) {
Amplifier amp = new Amplifier();
Tuner tuner = new Tuner();
DvdPlayer dvd = new DvdPlayer();
CdPlayer cd = new CdPlayer();
Projector projector = new Projector();
Screen screen = new Screen();
TheaterLights lights = new TheaterLights();
PopcornPopper popper = new PopcornPopper();
HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, tuner, dvd, cd, projector, lights, screen, popper);
homeTheater.watchMovie("Raiders of the Lost Ark");
System.out.println("\n-----------Watching Movies------------\n");
homeTheater.endMovie();
}
}
結果如下
外觀模式提供了一個統一的接口,用來訪問子系統中的一羣接口。外觀定義了一個高層接口,讓子系統更容易使用。
外觀模式有一個很好的特徵:提供簡化的接口的同時,依然將系統完整的功能暴露出來,以供需要的人使用。
以下是它的類圖
適配器模式的意圖是,“改變”接口符合客戶的期望;而外觀模式的意圖是,提供子系統的一個簡化接口。
我們將裝飾者模式、適配器模式、外觀模式一同比較,可以看出它們的意圖分別爲:
- 裝飾者模式,不改變接口,但加入職責
- 適配器模式,將一個接口轉成另一個接口
- 外觀模式,讓接口更簡單
設計原則:最少知識原則:只和你的密友談話。
當你正在設計一個系統,不管是任何對象,你都要注意它所交互的類有哪些,並注意它和這些類是如何交互的。
這個原則又名墨忒耳法則,它希望我們在設計中,不要讓太多的類耦合在一起,免得修改系統中一部分,會影響到其他部分。
這個原則提供了一些方針:就任何對象而言,在該對象的方法內,我們只應該調用屬於以下範圍的方法:
- 該對象本身
- 被當作方法的參數而傳遞進來的對象
- 此方法所創建或實例化的任何對象
- 對象的任何組件
如果調用從另一個調用中返回的對象的方法,會有什麼害處呢?如果我們這樣做,相當於向另一個對象的子部分發請求(而增加我們直接認識的對象數目)。
這個原則的缺點是,採用這個原則會導致更多的“包裝”類被製造出來,以處理和其他組件的溝通,這可能會導致複雜度和開發時間的增加,並降低運行時的性能。
總結如下:當我們需要從一個接口“轉變”爲另一個接口時,可以嘗試使用適配器;而當我們想要將子系統的一系列操作簡化時,可以使用外觀模式。