現實世界中的適配器模型
先來看下來幾個圖片,截圖自淘寶
上圖為港版的插頭與港版的插座
上圖為插座適配器賣家的描述圖
上圖為適配后的結果
現實世界中適配器模式 角色分類
這就是適配器模式在電源插座上的應用
我們看下在插座適配器中的幾個重要角色
可以看得出來,大陸和港版插座面板,都是作為電源的角色,他們的功能是相似的或者說相近的
插頭要使用插座,進而接通電流
現實世界到代碼的轉換 電源插座代碼示例
港版插座面板
package adapter; /**目標角色 Target 接口 * 香港地區使用的插座面板,提供輸出電流的功能 * @author noteless * */ public interface TargetHongkongPanelInterface { public void offerHongKongElectricity(); } package adapter; /**目標角色 Target 某個具體的港版插座面板 實現類 * 香港地區使用的插座面板,提供輸出電流的功能 * @author noteless * */ public class TargetHongkongPanel implements TargetHongkongPanelInterface{ @Override public void offerHongKongElectricity() { System.out.println("港版面板 提供電流"); } }
大陸地區插座面板
package adapter; /**被適配角色 Adaptee 接口 * 大陸地區使用的插座面板,提供輸出電流的功能 * @author noteless * */ public interface AdapteeChinaMainlandPanelInterface { public void offerChinaMainlandElectricity(); } package adapter; /**被適配角色 Adaptee 某種具體類型的插座面板 實現類 * 大陸地區使用的插座面板,提供輸出電流的功能 * @author noteless */ public class AdapteeChinaMainlandPanel implements AdapteeChinaMainlandPanelInterface{ @Override public void offerChinaMainlandElectricity() { System.out.println("國標面板 提供電流"); } }
港版插頭
package adapter; /**客戶角色 Client 港版插頭 * @author noteless * */ public class ClientHongKongSocket { /**接受港版插座面板作為參數 * 港版插頭,插入到港版插座面板 * @param targetHongkongPanel */ public void plugIn(TargetHongkongPanelInterface targetHongkongPanel) { targetHongkongPanel.offerHongKongElectricity(); } /* * 測試主程序,港版插頭 插入到適配器上 * 適配器插入到大陸面板上 */ public static void main(String ...args) { //港版插頭 ClientHongKongSocket socket = new ClientHongKongSocket(); //大陸面板 AdapteeChinaMainlandPanel adapteeChinaMainlandPanel = new AdapteeChinaMainlandPanel(); //適配器 Adapter adapter = new Adapter(adapteeChinaMainlandPanel); //港版插頭 插到 適配器上 socket.plugIn(adapter); } }
插頭適配器
package adapter; /**適配器角色 Adapter * 實現目標角色 TargetHongkongPanelInterface * 組合使用被適配角色 AdapteeChinaMainlandPanelInterface * 將對目標角色的方法調用轉換為被適配角色的方法調用 * @author noteless * */ public class Adapter implements TargetHongkongPanelInterface{ private AdapteeChinaMainlandPanelInterface adapteeChinaMainlandPanel; Adapter(AdapteeChinaMainlandPanel adapteeChinaMainlandPanel){ this.adapteeChinaMainlandPanel = adapteeChinaMainlandPanel; } @Override public void offerHongKongElectricity() { adapteeChinaMainlandPanel.offerChinaMainlandElectricity(); } }
執行港版插頭的測試main方法
UML圖
港版插頭ClientHongKongSocket與港版插座面板 TargetHongKongPanelInterface接口關聯
Adapter 實現了 港版插座面板 TargetHongKongPanelInterface接口
並且包含一個 大陸插座面板 AdapteeChinaMainlandPanelInterface 接口
適配器將對 港版插座面板的方法調用 轉換為 大陸插座面板的方法調用
這就是整個適配器結構( 可以不關注實現類)
客戶角色Client 要使用 目標角色Target
適配器模式就是要
冒充
目標角色Target,看起來有目標角色的行為
在OOP中,想要做到 就是實現或者繼承或者擁有一個成員
總之:
適配器就是把被適配者轉換為為目標
OOP中的適配器模式詳解
意圖:
將一個類的接口轉換成客戶希望的另外一個接口。
適配器模式使得原本由於接口不兼容而不能一起工作的那些類可以一起工作.
注意:此處說的接口,並不是單純的指Interface,而是指一切可以提供方法調用的類型,可能是接口也可能是類
|
客戶使用適配器的過程:
客戶通過目標接口調用適配器的方法,對適配器發出請求
適配器使用被適配者接口把請求進行處理
客戶接收到調用的結果,但是並未察覺這一切是適配器在起轉換作用.
|
適配器分類
適配器三種模式 |
類適配器 對象適配器 接口適配器 |
想要把一個類的接口轉換為客戶希望的另外一個接口 必須要有輸入輸出,有目標 有源 所以作為一個適配器,必須要 一手拿着被適配者也就是源 另一手拿着的是目標 想要轉變為目標,那么必須得同目標時一樣的類型, 在oop中想要成為目標類型 要么繼承 要么實現 想要擁有被適配者,要么繼承,要么實現,要么就是關聯(擁有一個對象) 三種方式可以理解為按照 擁有被適配者 的方式進行划分的 如果繼承Adaptee,那么就是類 適配器 如果擁有一個Adaptee,也就是擁有一個Adaptee對象,那么就是對象 適配器 如果實現Adaptee,那么就是 接口適配器 |
現在回想下,我們上面的例子
適配器 實現了目標接口,並且擁有一個Adaptee對象 作為屬性,很顯然就是對象適配器
類適配器
根據上面的描述,如果繼承Adaptee,那么就是類 適配器,
在Java中不允許多繼承,既然已經繼承了Adaptee ,那么就必須要求目標是一個接口(此處接口就是Interface)
這就有一定的局限性
而且,既然是繼承被適配者類,那么,被適配者的子類擁有的方法和行為,他並不能擁有,也就是說不能適配被適配者的子類
|
優點,那就是,適配器作為被適配者的子類,自然擁有更多的操作空間,比如重寫方法 |
對象適配器 |
如同我們上面的例子一樣,如果把被適配者當做一個屬性對象放到適配器中,這就是對象適配器
顯然,他不要求目標一定是接口, 繼承還是實現都可以
同類適配器比較的話,顯然,他不能對被適配者 原來的一些方法進行操作,只能進行使用,不過也無傷大雅,不算缺點
因為他是擁有一個被適配者類型的對象,那么,被適配者和他的子類顯然都可以作為具體的對象傳入
|
接口適配器 按照我們的描述,如果實現了被適配者 Adaptee 那么就是接口適配器 具體說來: 當不需要全部實現接口提供的方法時 可先設計一個抽象類實現接口,並為該接口中每個方法提供一個默認實現(空方法) 那么該抽象類的子類可有選擇地覆蓋父類的某些方法來實現需求 它適用於一個接口不想使用其所有的方法的情況 |
接口適配器示例
接口
package interfaceadapter; public interface Interfaces { public void method1(); public void method2(); public void method3(); public void method4(); public void method5(); }
抽象類
package interfaceadapter; /** * @author noteless * */ public abstract class AbstractClass implements Interfaces { @Override public void method1() { } @Override public void method2() { } @Override public void method3() { } @Override public void method4() { } @Override public void method5() { } }
兩個實現類
package interfaceadapter; public class ImplementClass1 extends AbstractClass { @Override public void method1() { System.out.println("method1 called "); } @Override public void method3() { System.out.println("method3 called "); } @Override public void method5() { System.out.println("method5 called "); } } package interfaceadapter; public class ImplementClass2 extends AbstractClass { @Override public void method2() { System.out.println("method2 called"); } @Override public void method4() { System.out.println("method4 called"); } }
測試類-客戶角色
package interfaceadapter; public class Test { public static void main(String[] args) { Interfaces class1 = new ImplementClass1(); Interfaces class2 = new ImplementClass2(); class1.method1(); class1.method2(); class1.method3(); class1.method4(); class1.method5(); System.out.println("------"); class2.method1(); class2.method2(); class2.method3(); class2.method4(); class2.method5(); } }
接口適配器的行為相當於適配了自己
把原來的接口 當做被適配者
目標則是一個實現了接口部分功能的類
調用這個接口的部分方法場景下,上面的形式是非常方便的
從這個示例中或許應該更加能理解適配器的本意:
將一個類的接口轉換成客戶希望的另外一個接口。
適配器模式使得原本由於接口不兼容而不能一起工作的那些類可以一起工作
所以說,但凡是涉及到轉換這一概念,你都可以考慮這個思維模式
三種常用形式,只是概念的表現形式而已
而且,實際的問題場景將會有很多種,也不可能完全都按照某種格式
再比如雙向適配器
即可以將被適配者轉換為目標
也可以把目標轉換為被適配者
雙向適配器
目標接口/目標實現類
package doubleadapter; public interface TargetInterface { void targetRequest(); } package doubleadapter; public class TargetImplClass implements TargetInterface{ @Override public void targetRequest() { System.out.println("targetRequest ... "); } }
被適配者接口/被適配者實現類
package doubleadapter; public interface AdapteeInterface { void adapteeRequest(); } package doubleadapter; public class AdapteeImplClass implements AdapteeInterface{ @Override public void adapteeRequest() { System.out.println("adapteeRequest ... "); } }
適配器
package doubleadapter; public class Adapter implements TargetInterface,AdapteeInterface { private TargetInterface target; private AdapteeInterface adaptee; Adapter(TargetInterface target){ this.target = target; } Adapter(AdapteeInterface adaptee){ this.adaptee = adaptee; } @Override public void adapteeRequest() { target.targetRequest(); } @Override public void targetRequest() { adaptee.adapteeRequest(); } }
Client 客戶端角色
Main方法就相當於Client 客戶端角色
適配器Adapter模式的宗旨是:
保留現有類所提供的服務,向客戶提供接口,以滿足客戶的期望,也就是將現有接口轉換為客戶希望的另外的一個接口
本質在於轉換
|
JDK中的小應用
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.apache.commons.collections.iterators; import java.util.Collection; import java.util.Enumeration; import java.util.Iterator;
public class EnumerationIterator implements Iterator { private Collection collection; private Enumeration enumeration; private Object last; public EnumerationIterator() { this((Enumeration)null, (Collection)null); } public EnumerationIterator(Enumeration enumeration) { this(enumeration, (Collection)null); } public EnumerationIterator(Enumeration enumeration, Collection collection) { this.enumeration = enumeration; this.collection = collection; this.last = null; } public boolean hasNext() { return this.enumeration.hasMoreElements(); } public Object next() { this.last = this.enumeration.nextElement(); return this.last; } public void remove() { if (this.collection != null) { if (this.last != null) { this.collection.remove(this.last); } else { throw new IllegalStateException("next() must have been called for remove() to function"); } } else { throw new UnsupportedOperationException("No Collection associated with this Iterator"); } } public Enumeration getEnumeration() { return this.enumeration; } public void setEnumeration(Enumeration enumeration) { this.enumeration = enumeration; } }
Enumeration 和 Iterator 大家應該都聽過
Enumeration算是遺留的老代碼了
很顯然,我們希望能夠使用新世界的Iterator
怎么辦呢?
答案就是適配器
目標是 Iterator 被適配者是 Enumeration
看代碼可知:
public class EnumerationIterator implements Iterator {
private Enumeration enumeration;
|
他實現了Iterator 並且有一個Enumeration 的成員,是 對象適配器
nextElement() 與 next()
hasMoreElements 與 hasNext()
他們可以說是匹配的
但是Enumeration 是不能刪除的,沒辦法搞remove方法
所以說源碼中提供了可以傳入集合的構造方法,把對應的集合也傳入進去
並且設置last變量記住剛才的位置
如果傳遞了集合 並且last存在,那么可以執行remove
否則拋異常
設計模式是作為解決問題或者設計類層級結構時的一種思維的存在,而不是公式一樣的存在!
|