適配器模式是將某個類的接口轉換成客戶端指望的另外一個接口表示,目的是消除因爲接口不匹配所形成的的類的兼容性問題。html
ADAPTER 適配器模式:在朋友聚會上碰到了一個美女Sarah,從香港來的,可我不會說粵語,她不會說普通話,只好求助於個人朋友kent了,他做爲我和Sarah之間的Adapter,讓我和Sarah能夠相互交談了(也不知道他會不會耍我) 適配器(變壓器)模式:把一個類的接口變換成客戶端所期待的另外一種接口,從而使本來因接口緣由不匹配而沒法一塊兒工做的兩個類可以一塊兒工做。適配類能夠根據參數返還一個合適的實例給客戶端。java
用電器作例子,筆記本電腦的插頭通常都是三相的,即除了陽極、陰極外,還有一個地極。而有些地方的電源插座卻只有兩極,沒有地極。電源插座與筆記本電腦的電源插頭不匹配使得筆記本電腦沒法使用。這時候一個三相到兩相的轉換器(適配器)就能解決此問題,而這正像是本模式所作的事情。程序員
主要分三類:類的適配器模式、對象的適配器模式、接口的適配器模式。sql
class Source { public void method1() { System.out.println("This is original method..."); } } interface Targetable { /** * 與原類中的方法相同 */ public void method1(); /** * 新類的方法 */ public void method2(); } class Adapter extends Source implements Targetable { @Override public void method2() { System.out.println("This is the targetable method..."); } } public class AdapterPattern { public static void main(String[] args) { Targetable targetable = new Adapter(); targetable.method1(); targetable.method2(); } }
基本思路和類的適配器模式相同,只是將Adapter 類做修改,此次不繼承Source 類,而是持有Source 類的實例,以達到解決兼容性的問題。數據庫
class Source { public void method1() { System.out.println("This is original method..."); } } interface Targetable { /** * 與原類中的方法相同 */ public void method1(); /** * 新類的方法 */ public void method2(); } class Wrapper implements Targetable { private Source source; public Wrapper(Source source) { super(); this.source = source; } @Override public void method1() { source.method1(); } @Override public void method2() { System.out.println("This is the targetable method..."); } } public class AdapterPattern { public static void main(String[] args) { Source source = new Source(); Targetable targetable = new Wrapper(source); targetable.method1(); targetable.method2(); } }
接口的適配器是這樣的:有時咱們寫的一個接口中有多個抽象方法,當咱們寫該接口的實現類時,必須實現該接口的全部方法,這明顯有時比較浪費,由於並非全部的方法都是咱們須要的,有時只須要某一些,此處爲了解決這個問題,咱們引入了接口的適配器模式,藉助於一個抽象類,該抽象類實現了該接口,實現了全部的方法,而咱們不和原始的接口打交道,只和該抽象類取得聯繫,因此咱們寫一個類,繼承該抽象類,重寫咱們須要的方法就行。編程
/** * 定義端口接口,提供通訊服務 */ interface Port { /** * 遠程SSH端口爲22 */ void SSH(); /** * 網絡端口爲80 */ void NET(); /** * Tomcat容器端口爲8080 */ void Tomcat(); /** * MySQL數據庫端口爲3306 */ void MySQL(); } /** * 定義抽象類實現端口接口,可是什麼事情都不作 */ abstract class Wrapper implements Port { @Override public void SSH() { } @Override public void NET() { } @Override public void Tomcat() { } @Override public void MySQL() { } } /** * 提供聊天服務 * 須要網絡功能 */ class Chat extends Wrapper { @Override public void NET() { System.out.println("Hello World..."); } } /** * 網站服務器 * 須要Tomcat容器,Mysql數據庫,網絡服務,遠程服務 */ class Server extends Wrapper { @Override public void SSH() { System.out.println("Connect success..."); } @Override public void NET() { System.out.println("WWW..."); } @Override public void Tomcat() { System.out.println("Tomcat is running..."); } @Override public void MySQL() { System.out.println("MySQL is running..."); } } public class AdapterPattern { private static Port chatPort = new Chat(); private static Port serverPort = new Server(); public static void main(String[] args) { // 聊天服務 chatPort.NET(); // 服務器 serverPort.SSH(); serverPort.NET(); serverPort.Tomcat(); serverPort.MySQL(); } }
缺省適配(Default Adapter)模式爲一個接口提供缺省實現,這樣子類型能夠從這個缺省實現進行擴展,而沒必要從原有接口進行擴展。做爲適配器模式的一個特例,缺省是適配模式在JAVA語言中有着特殊的應用。設計模式
和尚要作什麼呢?吃齋、唸經、打坐、撞鐘、習武等。若是設計一個和尚接口,給出全部的和尚都須要實現的方法,那麼這個接口應當以下:服務器
public interface 和尚 { public void 吃齋(); public void 唸經(); public void 打坐(); public void 撞鐘(); public void 習武(); public String getName(); }
顯然,全部的和尚類都應當實現接口所定義的所有方法,否則就根本通不過JAVA語言編輯器。像下面的魯智深類就不行。網絡
public class 魯智深 implements 和尚{ public void 習武(){ 拳打鎮關西; 大鬧五臺山; 大鬧桃花村; 火燒瓦官寺; 倒拔垂楊柳; } public String getName(){ return "魯智深"; } }
因爲魯智深只實現了getName()和習武()方法,而沒有實現任何其餘的方法。所以,它根本就通不過Java語言編譯器。魯智深類只有實現和尚接口的全部的方法才能夠經過Java語言編譯器,可是這樣一來魯智深就再也不是魯智深了。以史爲鑑,能夠知天下。研究一下幾百年前魯智深是怎麼剃度成和尚的,會對Java編程有很大的啓發。不錯,當初魯達剃度,衆僧說:「此人形容醜惡、相貌兇頑,不可剃度他",可是長老卻說:」此人上應天星、心地剛直。雖然時下兇頑,命中駁雜,久後卻得清淨。證果非凡,汝等皆不及他。」app
原來如此!看來只要這裏也應上一個天星的話,問題就解決了!使用面向對象的語言來講,「應」者,實現也;「天星」者,抽象類也。
public abstract class 天星 implements 和尚 { public void 吃齋(){} public void 唸經(){} public void 打坐(){} public void 撞鐘(){} public void 習武(){} public String getName(){ return null; } }
魯智深類繼承抽象類「天星」
public class 魯智深 extends 和尚{ public void 習武(){ 拳打鎮關西; 大鬧五臺山; 大鬧桃花村; 火燒瓦官寺; 倒拔垂楊柳; } public String getName(){ return "魯智深"; } }
這個抽象的天星類即是一個適配器類,魯智深實際上藉助於適配器模式達到了剃度的目的。此適配器類實現了和尚接口所要求的全部方法。可是與一般的適配器模式不一樣的是,此適配器類給出的全部的方法的實現都是「平庸」的。這種「平庸化」的適配器模式稱做缺省適配模式。
在不少狀況下,必須讓一個具體類實現某一個接口,可是這個類又用不到接口所規定的全部的方法。一般的處理方法是,這個具體類要實現全部的方法,那些有用的方法要有實現,那些沒有用的方法也要有空的、平庸的實現。
這些空的方法是一種浪費,有時也是一種混亂。除非看過這些空方法的代碼,程序員可能會覺得這些方法不是空的。即使他知道其中有一些方法是空的,也不必定知道哪些方法是空的,哪些方法不是空的,除非看過這些方法的源代碼或是文檔。
缺省適配模式能夠很好的處理這一狀況。能夠設計一個抽象的適配器類實現接口,此抽象類要給接口所要求的每一種方法都提供一個空的方法。就像幫助了魯智深的「上應天星」同樣,此抽象類可使它的具體子類免於被迫實現空的方法。
對於類適配器因爲適配器直接繼承了Adaptee,使得適配器不能和Adaptee的子類一塊兒工做,由於繼承是靜態的關係,當適配器繼承了Adaptee後,就不可能再去處理 Adaptee的子類了。
對於對象適配器一個適配器能夠把多種不一樣的源適配到同一個目標。換言之,同一個適配器能夠把源類和它的子類都適配到目標接口。由於對象適配器採用的是對象組合的關係,只要對象類型正確,是否是子類都無所謂。
對於類適配器適配器能夠重定義Adaptee的部分行爲,至關於子類覆蓋父類的部分實現方法。
對於對象適配器要重定義Adaptee的行爲比較困難,這種狀況下,須要定義Adaptee的子類來實現重定義,而後讓適配器組合子類。雖然重定義Adaptee的行爲比較困難,可是想要增長一些新的行爲則方便的很,並且新增長的行爲可同時適用於全部的源。
對於類適配器,僅僅引入了一個對象,並不須要額外的引用來間接獲得Adaptee。
對於對象適配器,須要額外的引用來間接獲得Adaptee。
建議儘可能使用對象適配器的實現方式,多用合成或聚合、少用繼承。固然,具體問題具體分析,根據須要來選用實現方式,最適合的纔是最好的。
適配器模式的優勢
更好的複用性:系統須要使用現有的類,而此類的接口不符合系統的須要。那麼經過適配器模式就可讓這些功能獲得更好的複用。
更好的擴展性:在實現適配器功能的時候,能夠調用本身開發的功能,從而天然地擴展系統的功能。
適配器模式的缺點
過多的使用適配器,會讓系統很是零亂,不易總體進行把握。好比,明明看到調用的是A接口,其實內部被適配成了B接口的實現,一個系統若是太多出現這種狀況,無異於一場災難。所以若是不是頗有必要,能夠不使用適配器,而是直接對系統進行重構。
適配器模式的用意是要改變源的接口,以便於目標接口相容。缺省適配的用意稍有不一樣,它是爲了方便創建一個不平庸的適配器類而提供的一種平庸實現。
在任什麼時候候,若是不許備實現一個接口的全部方法時,就可使用「缺省適配模式」製造一個抽象類,給出全部方法的平庸的具體實現。這樣,從這個抽象類再繼承下去的子類就沒必要實現全部的方法了。