一、基本概念 適配器模式是將某個類的介面轉換成客戶端期望的另一個介面表示,目的是消除由於介面不匹配所造成的的類的相容性問題。 二、通俗解釋 ADAPTER 適配器模式:在朋友聚會上碰到了一個美女Sarah,從香港來的,可我不會說粵語,她不會說普通話,只好求助於我的朋友kent了,他作為我和Sarah ...
一、基本概念
適配器模式是將某個類的介面轉換成客戶端期望的另一個介面表示,目的是消除由於介面不匹配所造成的的類的相容性問題。
二、通俗解釋
ADAPTER 適配器模式:在朋友聚會上碰到了一個美女Sarah,從香港來的,可我不會說粵語,她不會說普通話,只好求助於我的朋友kent了,他作為我和Sarah之間的Adapter,讓我和Sarah可以相互交談了(也不知道他會不會耍我) 適配器(變壓器)模式:把一個類的介面變換成客戶端所期待的另一種介面,從而使原本因介面原因不匹配而無法一起工作的兩個類能夠一起工作。適配類可以根據參數返還一個合適的實例給客戶端。
用電器做例子,筆記本電腦的插頭一般都是三相的,即除了陽極、陰極外,還有一個地極。而有些地方的電源插座卻只有兩極,沒有地極。電源插座與筆記本電腦的電源插頭不匹配使得筆記本電腦無法使用。這時候一個三相到兩相的轉換器(適配器)就能解決此問題,而這正像是本模式所做的事情。
三、分類
主要分三類:類的適配器模式、對象的適配器模式、介面的適配器模式。
1. 類的適配器模式:
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();
}
}
2. 對象的適配器模式
基本思路和類的適配器模式相同,只是將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();
}
}
3. 介面的適配器模式
介面的適配器是這樣的:有時我們寫的一個介面中有多個抽象方法,當我們寫該介面的實現類時,必須實現該介面的所有方法,這明顯有時比較浪費,因為並不是所有的方法都是我們需要的,有時只需要某一些,此處為瞭解決這個問題,我們引入了介面的適配器模式,藉助於一個抽象類,該抽象類實現了該介面,實現了所有的方法,而我們不和原始的介面打交道,只和該抽象類取得聯繫,所以我們寫一個類,繼承該抽象類,重寫我們需要的方法就行。
/**
* 定義埠介面,提供通信服務
*/
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編程有很大的啟發。不錯,當初魯達剃度,眾僧說:“此人形容醜惡、相貌凶頑,不可剃度他",但是長老卻說:”此人上應天星、心地剛直。雖然時下凶頑,命中駁雜,久後卻得清凈。證果非凡,汝等皆不及他。”
原來如此!看來只要這裡也應上一個天星的話,問題就解決了!使用面向對象的語言來說,“應”者,實現也;“天星”者,抽象類也。
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介面的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用適配器,而是直接對系統進行重構。
適配器模式的用意是要改變源的介面,以便於目標介面相容。預設適配的用意稍有不同,它是為了方便建立一個不平庸的適配器類而提供的一種平庸實現。
在任何時候,如果不准備實現一個介面的所有方法時,就可以使用“預設適配模式”製造一個抽象類,給出所有方法的平庸的具體實現。這樣,從這個抽象類再繼承下去的子類就不必實現所有的方法了。
(引用:《JAVA與模式》之適配器模式、Java 中幾種常用設計模式、java常用的設計模式)