
為了防止被“殺”了祭天,學(xué)點(diǎn)設(shè)計(jì)模式,并總結(jié)下還是有必要的。
一:理解
- 適配器模式讓一個(gè)類的接口,轉(zhuǎn)換成客戶期望的另一個(gè)接口。適配器讓原本接口不兼容的類可以合作無間?!禜ead First設(shè)計(jì)模式》
- 適配器模式在生活中也經(jīng)常用到,如不同標(biāo)準(zhǔn)的插座適配器。
二:例子
你是個(gè)富二代。
在全新MacBook上市之后,你準(zhǔn)備給你司兩萬多名員工換上最新的Mac。
但你發(fā)現(xiàn),新的MacBook只有TypeC接口,之前那些使用HDMI接口連接的顯示屏都不能用,氣得你都想打人。

你希望Mac可以適配以下這些接口。

于是,你找到了程序員小菜幫忙解決這個(gè)接口適配問題。
小菜表示,新款Mac只提供TypeC接口,怪我咯。

不過,由于你是富二代,小菜也只能乖乖地敲起了代碼。
他首先抽象了TypeC接口,和一個(gè)TypeC類:
public interface TypeCInterface {
void connectTypeC(String device, String port);
}
public class TypeC implements TypeCInterface {
@Override
public void connectTypeC(String device, String port) {
if (StringUtils.equals(port, "typeC")) {
System.out.println("使用TypeC接口連接" + device);
} else {
throw new UnsupportedOperationException("Not supported");
}
}
}
TypeCInterface接口中申明了connectTypeC方法,兩個(gè)參數(shù)device和port,表示待連接設(shè)備的名稱和接口。例如使用HDMI連接的顯示屏,使用USB接口連接的U盤等。
TypeC類實(shí)現(xiàn)TypeCInterface接口,并實(shí)現(xiàn)connectTypeC方法,該方法需要檢查待連接的設(shè)備是否使用typeC接口,符合要求才進(jìn)行連接,否則拋出不支持操作異常。
小菜接著抽象了MacBook類:
@Data
public class MacBook {
private TypeCInterface typeC;
public void connect(String device, String port) {
typeC.connectTypeC(device, port);
}
}
MacBook包含一個(gè)typeC接口,和一個(gè)連接方法,連接時(shí)只能通過TypeC接口連接,即調(diào)用typeC屬性的connectTypeC方法。
小菜寫了一段測試代碼,分別嘗試在MacBook上連接typeC接口的顯示屏和hdmi接口的顯示屏。
public class Client {
public static void main(String[] args) {
TypeCInterface macTypeC = new TypeC();
MacBook macBook = new MacBook();
macBook.setTypeC(macTypeC);
macBook.connect("Display", "typeC");
macBook.connect("Display", "hdmi");
}
}
輸入/輸出:
使用TypeC接口連接Display
Exception in thread "main" java.lang.UnsupportedOperationException: Not supported
結(jié)果很明顯,新款MacBook不支持HDMI接口的顯示屏。
為了能連接HDMI接口的顯示屏,他又抽象了HDMI接口,和一個(gè)HDMI類:
public interface HDMIInterface {
void connectHDMI(String device, String equipment);
}
public class HDMI implements HDMIInterface {
@Override
public void connectHDMI(String device, String port) {
if (StringUtils.equals(port, "hdmi")) {
System.out.println("使用HDMI接口連接" + device);
} else {
throw new UnsupportedOperationException("Not supported");
}
}
}
HDMI接口和類的定義和TypeC接口類似。
然而MacBook是無法拆的,不能在Mac上增加一個(gè)HDMI接口。
于是小菜想到可以在某寶上買個(gè)適配器來解決這個(gè)問題。

很顯然的,這個(gè)接口需要符合以下兩點(diǎn)要求:
- 實(shí)現(xiàn)TypeC接口,用于連接MacBook,即將該接口的對(duì)象set進(jìn)MacBook的typeC屬性中。
- 支持連接實(shí)現(xiàn)HDMI接口的設(shè)備。
小菜很開心,立馬寫了一個(gè)HDMITypeCAdapter類:
@Data
public class HDMITypeCAdapter implements TypeCInterface {
HDMIInterface hdmi;
@Override
public void connectTypeC(String device, String port) {
System.out.println("裝上HDMITypeC適配器");
if (StringUtils.equals(port, "hdmi")) {
hdmi.connectHDMI(device, port);
} else {
throw new UnsupportedOperationException("Not supported");
}
}
}
該類實(shí)現(xiàn)了TypeC接口(TypeCInterface),并包含一個(gè)hdmi屬性,表示支持連接實(shí)現(xiàn)了HDMI接口的設(shè)備。
在connectTypeC方法中,首先輸出裝了適配器,然后再調(diào)用hdmi對(duì)象的connectHDMI方法。
連上適配器之后,小菜寫了測試代碼:
public class Client {
public static void main(String[] args) {
TypeCInterface macTypeC = new TypeC();
MacBook macBook = new MacBook();
macBook.setTypeC(macTypeC);
macBook.connect("Display", "typeC");
HDMITypeCAdapter hdmiTypeCAdapter = new HDMITypeCAdapter();
HDMIInterface macHDMI = new HDMI();
hdmiTypeCAdapter.setHdmi(macHDMI);
macBook.setTypeC(hdmiTypeCAdapter);
macBook.connect("Display", "hdmi");
}
輸入/輸出:
使用TypeC接口連接Display
裝上HDMITypeC適配器
使用HDMI接口連接Display
這就是適配器模式,用上適配器之后,可以讓調(diào)用方MacBook在不改變?cè)写a的情況下,調(diào)用不適配的HDMI接口。
小菜又想到,如果你哪天想要在MacBook上連接其他設(shè)備了怎么辦,只能再買一個(gè)新的適配器,如TypeC轉(zhuǎn)USB適配器。
如果要符合你之前提出的適配多借口的需求,那就需要買多個(gè)適配器。這樣會(huì)變得很麻煩。
于是小菜搜了一下某寶,發(fā)現(xiàn)了一枚神器,多功能適配器。

小菜抽象了一個(gè)多功能適配器類:
public class MultifunctionTypeCAdapter implements TypeCInterface {
private static Map<String, Object> portMap = Maps.newHashMap();
static {
portMap.put("typeC", new TypeC());
portMap.put("hdmi", new HDMI());
portMap.put("usb", new USB());
}
@Override
public void connectTypeC(String device, String port) {
if (!portMap.containsKey(port)) {
throw new UnsupportedOperationException("Not supported");
}
System.out.println("裝上多功能適配器");
if (StringUtils.equals(port, "typeC")) {
((TypeC) portMap.get("typeC")).connectTypeC(device, port);
} else if (StringUtils.equals(port, "hdmi")) {
((HDMI) portMap.get("hdmi")).connectHDMI(device, port);
} else if (StringUtils.equals(port, "usb")) {
((USB) portMap.get("usb")).connectUSB(device, port);
}
}
}
可以看到,多功能TypeC適配器仍舊實(shí)現(xiàn)了TypeC接口,并用一個(gè)Map來保存所有支持的接口,該多功能適配器支持轉(zhuǎn)接TypeC,HDMI和USB。
在connectTypeC方法中,首先判斷portMap中是否包含輸入中指定的接口port,確定之后再進(jìn)行連接。
其中的USB類如下:
public class USB {
public void connectUSB(String device, String port) {
if (StringUtils.equals(port, "usb")) {
System.out.println("使用USB接口連接" + device);
} else {
throw new UnsupportedOperationException("Not supported");
}
}
}
小菜把多功能TypeC適配器拿給你看,你覺得非常nice,并表示“我覺得OK”。

于是你興高采烈地去采購了一批多功能適配器。
小菜以為你會(huì)慷慨地獎(jiǎng)勵(lì)他一臺(tái)新的MacBook,開心得像個(gè)兩百斤的孩子。
然而,作為富二代的你,覺得適配器比Mac有意思多了,就決定賞賜小菜幾個(gè)適配器。

于是,小菜只能默默地玩起了適配器適配適配器的游戲,并且深藏功與名。
三:再理解
- 調(diào)用者持有原有接口屬性,調(diào)用原有接口的方法,并且不能修改。
- 調(diào)用者需要調(diào)用新的接口,由于新舊接口不兼容,不能把新接口的對(duì)象直接set進(jìn)調(diào)用者的屬性。
- 只能新建一個(gè)實(shí)現(xiàn)老接口的適配器類,持有新接口對(duì)象,在適配器類的方法體內(nèi)調(diào)用新接口的方法。
- 當(dāng)需要再次調(diào)用別的新接口時(shí),只需要增加新的適配器類。符合對(duì)增加開放,對(duì)修改關(guān)閉的原則。