DesignPattern之Adapter和Facade
迪米特原則(”Least Knowledge”原則)
Adapter 適配器模式(主要目的:轉化接口!)
1.定義
將一個類的接口,轉換成客戶期望的另一個接口。適配器讓原來接口不兼容的類可以合作無間。
個人理解:適配器模式,就好比轉換插。我的電腦的充電器是英標三腳的插頭,無法插進歐標的二腳插座,要解決這個問題,只需要一個歐標轉換插,電腦充電器插入這個轉換插,然後這個轉換插再插進歐標的插座就可以解決。而且不僅是電腦充電器,凡是英標三腳的插頭的電器都能使用這個轉換插(只要實現了接口,被適配者的任何子類,都可以搭配)。
適配器模式可以通過創建適配器進行接口轉換,讓原來不兼容的接口變成兼容。這可以讓客戶從實現的接口解耦。如果在一段時間後想要改變接口,適配器可以將改變的部分封裝起來,客戶就不必爲了應對不同的接口而每次跟着修改。
適配器模式充滿着良好的OO設計原則:使用對象組合,以修改的接口包裝被適配者(適配器這個類中持有被適配者的引用)–> 額外優點:被適配者的任何子類都可以搭配適配器使用。
- 因爲客戶是和接口綁定起來(面向接口編程),而不是和實現綁定起來,所以可以使用數個適配器,每一個負責轉換不同的後臺類;或者也可以加上新的實現,==只要實現了目標接口即可!==
2.例子:
使用火雞搭配適配器來冒充鴨子
鴨子接口:
public interface Duck {
public void quack();
public void fly();
}
綠頭鴨實現了鴨子這個接口:
public class MallarDuck implements Duck {
@Override
public void quack() {
System.out.println("Quack!");
}
@Override
public void fly() {
System.out.println("MallarDuck 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 am flying a short distance!");
}
}
現在缺少鴨子對象,想用一些火雞對象來冒充鴨子
–> 使用火雞-->鴨子
適配器!!!!
// 首先這個適配器類需要實現想轉換成的類型的接口,也就是客戶所期望看到的接口
// 就好像前面所說的轉換插,如果這個轉換插不實現期望的接口(不是二腳歐標插口),
// 那麼這個轉換插並沒有什麼卵用!
//
public class TurkeyAdapter implements Duck {
// 需要取得適配的對象(被適配者)的引用
private 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 AdapterTest {
public static void main(String[] args) {
MallarDuck mDuck = new MallarDuck();
WildTurkey turkey = new WildTurkey();
// 將火雞包裝進一個火雞適配器中,使他看起來像是一隻鴨子
// 注意這裏的類型是鴨子接口,就是說在客戶看來這是一隻鴨子
Duck turkeyAdapter = new TurkeyAdapter(turkey);
System.out.println("\nThe Turkey says...");
turkey.gobble();
turkey.fly();
System.out.println("\nDuck says...");
testDuck(mDuck);
System.out.println("\nThe turkeyAdapter says...");
// 因爲turkeyAdapter的類型是Duck,所以完全可以作爲參數傳進testDuck方法
// 但Duck的外表下其實是一種火雞。。。坑爹啊!!!
testDuck(turkeyAdapter);
}
private static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}
3.java中的適配器模式
java早期的集合類型(Vector,Stack, Hashtable等)都有一個名爲elements()
的方法,該方法的返回值類型爲Enumeration
, Enumeration
裏有兩個方法hasMoreElements()
, nextElement()
。
Sun推出更新後的集合類是,開始使用Iterator
接口,與Enumeration
類似,但還提供了remove()
方法。
面對遺留代碼,這些代碼暴露出枚舉類接口,但是我們想在新的代碼中使用迭代器?
–> 使用一個適配器即可。
按照前面鴨子的例子:
Enumeration接口:
public interface Enumeration {
public void hasMoreElements();
public void nextElement();
}
Iterator接口:
public interface Iterator {
public boolean hasNext();
public Object next();
public void remove();
}
重頭戲:
適配器!
public class EnumerationAdapter implements Iterator {
private Enumeration enum;
public EumerationAdapter(Enumeration enum) {
this.enum = enum;
}
@Override
public boolean hasNext() {
return this.enum.hasMoreElements();
}
@Override
public void next() {
this.enum.nextElement();
}
// 我們知道枚舉不支持刪除,因爲枚舉是一個只讀的接口。
// 適配器無法實現一個有實際功能的remove方法
// 幸運的時, 迭代器接口的設計者事先料到了會有這樣的需要
// 所以將remove()方法定義成會拋出 UnsupportedOperationException
@Override
public void remove() {
throw new UnsupportedOperationException();
}
}
2. Facade 外觀模式(主要目的:簡化接口!)
定義
–
提供了一個統一的接口,用來訪問子系統中的一羣接口。外觀模式定義了一個高層接口, 讓子系統更容易使用。
例子
–
假設家裏有一套家庭影院
觀賞電影(複雜的方式)
- 打開爆米花機
- 開始爆米花
- 將燈光調暗
- 放下屏幕
- 打開投影機
- 將投影機的輸入切換到DVD
- 投影機設置成寬屏模式
- 打開功放
- 將功放的輸入設置爲DVD
- 將功放設置爲環繞立體聲
- 打開DVD播放器
- 開始播放DVD
映射成代碼:
popper.on();
popper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.setInput(dvd);
projector.wideScreenMode();
amp.on();
amp.setDvd(dvd);
amp.setSurroundSound();
amp.setVolume(5);
dvd.on();
dvd.play();
媽蛋,如果軟甲設計成這麼麻煩,哪裏會有客戶會用這個軟件!!!
所以要使用==外觀模式==,通過實現一個體更合理的接口的外觀類,將一個複雜的子系統變得易用。
在這裏,可以把上面的代碼封裝進一個watchMovie()
方法中,客戶只需要調用這個方法,就可以完成一系列的準備工作(好比按下遙控器上的一個按鈕,然後系統就自動準備好。)
public class HomeTheaterFacade {
AMplifier amp;
Tuner tuner;
DvdPlayer dvd;
cdPlayer cd;
Projector projector;
TheaterLights lights;
Screen screen;
PopcornPopper popper;
public HomeTheaterFacad(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.screen = screen;
this.lights = lights;
this.popper = popper;
}
//外觀模式!!!
public void watchMovie(String movie) {
popper.on();
popper.pop();
lights.dim(10);
screen.down();
projector.on();
projector.setInput(dvd);
projector.wideScreenMode();
amp.on();
amp.setDvd(dvd);
amp.setSurroundSound();
amp.setVolume(5);
dvd.on();
dvd.play();
}
}
客戶類:
public class Client {
public static void main(String[] args) {
HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp,
tuner, dvd, cd, projector, screen, lights, popper);
// 客戶只需要調用watchMovie方法便可以看電影了,
// 比沒有使用外觀模式之前方便了無數倍!!!!
homeTheater.watchMovie("Matrix");
}
}
注意:外觀模式並沒有“封裝“子系統的類,只是提供了簡化的接口。所以如果客戶覺得有必要,仍然可以直接使用子系統的類。這也是外觀模式的一個優點:==提供簡化的接口的同時,依然將系統外爭的功能暴露出來,以供需要的人使用。==
3.迪米特原則(”Least Knowledge”原則)
–
這個原則告訴我們要減少對象之間的交互,只留下幾個”密友“,讓朋友圈維持在最小狀態!!!!
這是說:當你在設計一個系統的時候,不管是任何對象,你都要注意它所交互的類有哪些,並注意這些類是如何交互的。
==–>不要讓太多的類耦合在一起(低耦合!!!), 免得修改系統中一部分,會影響到其他部分==。
如果許多類之間相互依賴,那麼這個系統就會變成一個易碎的系統,而且極其複雜(想象一下一堆毛線纏繞在一起。。。)
如何做到這個原則?
–
就任何對象而言,在該對象的方法內,只應該調用屬於以下範圍的方法:
該對象本身的
被當做方法的參數而傳遞進來的對象
此方法所創建或實例化的對象
前面三條告訴我們:如果某對象調用其他方法的方法,不要調用該對象的方法!!!!
對象的任何組件 (該類的屬性的對象(該類持有對方引用))
調用從另一個調用中返回的對象的方法,會有什麼壞處?
–
如果這樣做,相當於想另一個對象的子部分發送請求,從而增加了直接認識的對象數目 –> 該類依賴的類的數目增加,系統的穩定性下降!
例子1
–
不採用這個原則:
public float getTemp() {
Thermometer thermometer = station.getThermometer();
return thermometer.getTemperature();
}
這裏的termometer對象時由station的方法返回而來,這樣做的結果是:當前的類與Thermometer這個類產生了關聯,相當於當前類要認識Thermometer和Station兩個類。。。
==採用這個原則==
在Station中添加一個方法,用來向Thermometer取得溫度,這樣就可以從Station這個類返回溫度, 當前類無需與Thermometer產生關聯(降低耦合!)
public float getTemp() {
return station.getTemperature();
}
外觀模式和最少知識原則
–
回想一下前面的家庭影院的設計,如果不採用外觀模式,那麼客戶這個類必須要與Tuner,Screen,CdPlayer等各種類打交道,類與類之間的聯繫十分複雜。但是採用外觀模式之後,客戶只需要與HomeTheaterFacade這個類打交道,調用watchMovie方法便可。類與類之間的關聯、複雜度大大降低了!
試着讓子系統也能遵守這個原則:如果子系統太複雜,有太多的朋友牽涉其中,那麼我們可以增加更多的外觀,將此子系統分成好幾個層次。
裝飾者、適配器、外觀三種模式對比
模式 | 目的 |
---|---|
裝飾者 | 不改變藉口,但加入新功能、新責任 |
適配器 | 將一個接口轉換成用戶需要的接口 |
外觀 | 使接口更簡單易用 |