目錄
1、什麼是擴充卡模式
2、擴充卡模式的适用場景
3、擴充卡模式的結構
4、擴充卡模式應用舉例
5、擴充卡模式的選擇
參考文章
1、什麼是擴充卡模式
擴充卡(Adapter)模式又叫做包裝( Wrapper )模式,是由GOF提出的23種設計模式中的一種結構型設計模式,Adapter模式的設計意圖:将一個類的接口轉換成客戶希望的另外一個接口,使得原本由于接口不相容而不能一起工作的那些類可以在一起工作。
2、擴充卡模式的适用場景
如圖所示,有時,為複用而設計的工具類常常會因為它的接口與專業應用領域所需要的接口不比對而不能夠被複用。
再舉個Java界面程式開發中的真實案例:我們要設計一個繪圖編輯器,這個編輯器允許使用者繪制和排列基本圖元(線、多邊形、正文等),圖元對象的抽象我們用一個 Shape 接口來定義,所有的具體圖元類型都要實作 Shape 接口:LineShape類對應于直線,PolygonShape類對應于多邊形,TextShape類對應于正文,等等。
對于像LineShape和PolygonShape這樣的基本圖元類由于它們的編輯功能本來就很有限,我們很容易就能夠實作。但是對于可以顯示和編輯正文的TextShape子類來說,實作相當困難,因為即使是基本的正文編輯也要涉及到複雜的螢幕重新整理和緩沖區管理。同時,外界可能已經存在了一個現成的工具類TextView可以用于顯示和編輯正文。理想的情況是我們可以複用這個TextView類以實作TextShape類的功能,不巧的是這個TextView類的設計者當時并沒有考慮 Shape 的存在,導緻TextView類和 Shape的接口互不相容。
類似地,像上面這種情況,我們希望能夠複用TextView這樣已經存在的類,但此類與我們系統要求的接口不比對,我們該如何處理呢?
我們可以改變TextView類使它相容Shape接口,但前提是必須有這個TextView類的源代碼。然而即使我們得到了這些源代碼,修改TextView也是沒有什麼意義的:因為不應該僅僅為了實作一個應用,就去修改那些為複用而設計的工具箱類,迫使它們實作與特定領域相關的接口(此處是 Shape 接口)。
我們可以不用上面的方法,轉而定義一個TextShape類,由它來适配TextView的接口和Shape的接口。我們可以用下面兩種方法做成這件事:
1)将TextShape繼承TextView并實作Shape接口
2)将一個TextView執行個體作為TextShape的組成部分,并且使用TextView的接口去實作TextShape接口。
以上兩種方法恰恰對應于Adapter模式的兩個版本:類的擴充卡模式和對象的擴充卡模式。我們将TextShape稱之為擴充卡Adapter。
從以上案例可以看出,擴充卡模式其實是一種補償型模式,在進行全新系統設計的時候很少會用到。而當你遇到以下情況,你或許可以考慮使用Adapter模式。
-你想使用一個已經存在的類,而它的接口不符合你的需求。
-你想建立一個可以複用的類,該類可以與其他不相關的類或不可預見的類(即那些接口可能不一定相容的類)協同工作。
-(僅适用于對象Adapter)你想使用一些已經存在的子類,但是不可能對每一個都進行子類化以比對它們的接口。
3、擴充卡模式的結構
擴充卡模式分為類的擴充卡模式(采用繼承實作)、對象的擴充卡模式(采用對象組合方式實作)和接口的擴充卡模式三種。
類擴充卡通過繼承對一個類與另一個接口進行比對,如下圖所示。
類擴充卡模式涉及的角色及其職責如下:
用戶端(Client)類:該類需要與符合條件的特定的接口協同工作。
目标(Target)接口類:用戶端所需要的接口,在類擴充卡模式下該角色隻能是接口。
适配者(Adaptee)類:需要被适配的類,适配者類一般是一個具體類,包含了用戶端希望使用的某些業務方法。
擴充卡(Adapter)類:該類對适配者類和目标接口類進行适配,在類擴充卡模式下通過繼承方式實作,即:
Adapter 繼承 Adaptee 并實作 Target 接口。
類擴充卡模式結構示意源代碼如下:
Target類包含Client類所需要的與特定領域相關的接口。
public interface Target {
// Adaptee适配者有此方法的實作,但方法名可以不同
void specificOperation();
// Adaptee适配者沒有的其他方法
void otherOperation();
}
Adaptee類包含了用戶端希望使用的某些業務方法,但Adaptee類不符合Client類所需接口的要求。
public class Adaptee {
public void operation() {
System.out.println("執行Adaptee的operation()方法...");
}
}
Adapter類繼承Adaptee并實作Target接口,這樣Adapter類既符合Client類所需接口的要求,又包含了Client類希望使用的而原屬于Adaptee類的業務方法。
public class Adapter extends Adaptee implements Target {
@Override
public void specificOperation() {
this.operation();
}
@Override
public void otherOperation() {
System.out.println("執行Adapter的otherOperation()方法...");
}
}
為簡單起見我們為Client類添加一個clientOperation()方法,該方法需要傳入一個Target接口對象,在該Target接口對象中我們要複用現有的Adaptee類的方法。
public class Client {
public static void clientOperation(Target target) {
target.specificOperation();
target.otherOperation();
}
public static void main(String[] args) {
Adapter adapter = new Adapter();
clientOperation(adapter);
}
}
運作程式列印結果如下:
執行Adaptee的operation()方法...
執行Adapter的otherOperation()方法...
對象擴充卡通過組合對一個類及其子類與另一個接口進行比對,如下圖所示。
擴充卡模式涉及的角色及其職責如下:
用戶端(Client)類:該類需要與符合條件的特定的接口協同工作。
目标(Target)接口類:用戶端所需要的接口,在對象擴充卡模式下該角色可以是接口、抽象類或者非final的具體類。
适配者(Adaptee)類:需要被适配的類,适配者類一般是一個具體類,包含了用戶端希望使用的某些業務方法。
擴充卡(Adapter)類:該類對适配者類和目标接口類進行适配,在對象擴充卡模式下通過組合方式實作,即:Adapter類繼承Target類或者實作Target接口,并在其内部包含一個Adaptee對象的引用,通過對其内部的Adaptee對象的調用實作用戶端所需要的接口。
接下來以Target為接口舉例,對象擴充卡模式結構示意源代碼如下:
Target類包含Client類所需要的與特定領域相關的接口。
public interface Target {
// Adaptee适配者有此方法的實作,但方法名可以不同
void specificOperation();
// Adaptee适配者沒有的其他方法
void otherOperation();
}
Adaptee 包含了 Client 希望使用的某些業務方法,但 Adaptee 不符合 Client 的接口要求。
public class Adaptee {
public void operation() {
System.out.println("執行Adaptee的operation()方法...");
}
}
Adapter 實作 Target 接口,并在其内部包含一個 Adaptee 對象的引用,通過對其内部的 Adaptee 對象的方法調用來實作用戶端所需要的接口。
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
super();
this.adaptee = adaptee;
}
@Override
public void specificOperation() {
this.adaptee.operation();
}
@Override
public void otherOperation() {
System.out.println("執行Adapter的otherOperation()方法...");
}
}
同樣的,Client 依舊要與一個 Target 接口協同工作,對Client進行簡單修改。
public class Client {
public static void clientOperation(Target target){
target.specificOperation();
target.otherOperation();
}
public static void main(String[] args) {
Adapter adapter = new Adapter(new Adaptee());
clientOperation(adapter);
}
}
運作程式列印結果如下:
執行Adaptee的operation()方法...
執行Adapter的otherOperation()方法...
接口擴充卡模式又被叫作預設擴充卡(DefaultAdapter)模式,DefaultAdapter 為一個接口提供預設實作,這樣需實作該接口的類就可以直接從 DefaultAdapter 進行擴充,而不必再從原有接口進行擴充。當原接口中定義的方法很多,而其中大部分方法又不被需要時,這種模式非常實用。由預設擴充卡類(由于該類一般都隻為接口提供預設的空實作,是以該類一般都被定義為抽象類)直接實作接口,并為所有方法提供預設實作。這樣,如果有使用者類需要實作該接口就可以直接繼承擴充卡類,并隻需實作感興趣的方法就可以了。
預設擴充卡模式涉及的角色及其職責如下:
目标(Target)接口類:使用者類所需要實作的接口,定義有很多方法,但這些方法不一定全都被使用者類所需要。
預設擴充卡(DefaultAdapter)類:實作目标接口,并為所有接口方法提供預設實作。
具體(ConcreteClass)使用者類:使用者類要實作某一個接口,但是又用不到接口所規定的所有的方法。
類擴充卡模式結構示意源代碼如下:
Target接口類是使用者類所需要實作的接口,該接口定義有很多方法。
public interface Target {
public void operation1();
public void operation2();
public void operation3();
public void operation4();
public void operation5();
public void operation6();
}
DefaultAdapter類實作Target接口,并為所有接口方法提供預設實作。
public class DefaultAdapter implements Target {
public void operation1() {
System.out.println("執行預設擴充卡的operation1()方法...");
}
public void operation2() {
System.out.println("執行預設擴充卡的operation2()方法...");
}
public void operation3() {
System.out.println("執行預設擴充卡的operation3()方法...");
}
public void operation4() {
System.out.println("執行預設擴充卡的operation4()方法...");
}
public void operation5() {
System.out.println("執行預設擴充卡的operation5()方法...");
}
public void operation6() {
System.out.println("執行預設擴充卡的operation6()方法...");
}
}
接下來就要定義使用者類了,我們定義兩個具體使用者類 ConcreteClassA 和 ConcreteClassB ,兩個類都隻實作 Target 接口中的部分方法。
public class ConcreteClassA extends DefaultAdapter {
public void operation3() {
System.out.println("執行ConcreteClassA的operation3()方法...");
}
}
public class ConcreteClassB extends DefaultAdapter {
public void operation4() {
System.out.println("執行ConcreteClassB的operation4()方法...");
}
public void operation5() {
System.out.println("執行ConcreteClassB的operation5()方法...");
}
}
在很多情況下,使用者類會需要實作某一個接口,但是又用不到接口所規定的所有的方法。通常的處理方法是,使用者類要實作所有的方法,為那些有用的方法添加實作,為那些沒有用的方法添加空的、平庸的實作。為不需要的方法添加空實作其實是一種浪費,有時也是一種混亂。除非看過這些方法的源代碼或文檔,程式員可能會以為這些方法不是空的。即便他知道其中有一些方法是空的,也不一定知道哪些方法是空的,哪些方法不是空的。預設适配模式可以很好的處理這一情況。
從以上兩個具體使用者類 ConcreteClassA 和 ConcreteClassB的代碼可以看出:兩個使用者類通過繼承預設擴充卡類,而無需再為接口中的全部方法添加實作。
4、擴充卡模式應用舉例
應用場景:
SD記憶體卡是一種很常見的手機外置儲存設備,我們可以通過給手機插入一個SD卡來擴充手機的存儲空間, 當SD卡中存儲的檔案很多需要整理的時候問題來了,直接在手機上對SD卡中的檔案進行整理(拷貝、删除、移動、修改等等)操作起來很不友善,于是我們想如果能夠将SD卡插入電腦,然後通過電腦對SD卡上的檔案進行整理該多友善。可不幸的是,大多數的電腦僅能連接配接具有USB接口的裝置,顯然,SD卡并不具備USB接口。
以上是擴充卡模式的一個典型應用場景,SD卡是專門為手機而設計的,在設計之初也并未想過要去把它插入電腦。現在我們想要把SD卡插入到電腦了,卻發現SD卡因不具有USB插頭而不能插入電腦。此時,再去重新設計和生産一種新型的具有USB接口的SD卡顯然也并不合理。但是,很明顯SD卡與U盤等USB接口裝置都是儲存設備,二者并無本質差別,理論上來說是完全可以插入電腦的。那麼我們到底該怎麼辦呢?
以上問題的解決方法其實很簡單:找一個USB接口的讀卡器,将SD卡插入到讀卡器,再将讀卡器插入電腦,此時你會發現SD卡連上電腦了。
上例中的讀卡器其實就是一個适配SD卡和USB接口的擴充卡,接下來我們用Java代碼來進行示範說明。
首先,為了使示範更加清晰,我們重新定義一個File類,用來表示SD卡中存儲的檔案。
public class File {
//檔案名
private String fileName;
//檔案大小
private Double fileSize;
//構造方法中指定檔案名和檔案大小
public File(String fileName, Double fileSize) {
super();
this.fileName = fileName;
this.fileSize = fileSize;
}
public String getFileName() {
return fileName;
}
public Double getFileSize() {
return fileSize;
}
}
我們的SD卡類提供了基本的檔案添加、檔案删除、顯示已存儲檔案的功能,其完整代碼如下。
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
public class SDCard {
// SD卡的存儲空間總大小
private Double volume;
// SD卡的可用存儲空間大小
private Double vacantVolume;
// 将SD卡中存儲的檔案儲存到一個Map裡面
private Map<String, File> fileMap = new HashMap<String, File>();
public SDCard(Double volume) {
super();
this.volume = volume;
this.vacantVolume = volume;
}
public Double getVacantVolume() {
return vacantVolume;
}
public void setVacantVolume(Double vacantVolume) {
this.vacantVolume = vacantVolume;
}
public Double getVolume() {
return volume;
}
public Map<String, File> getFileMap() {
return fileMap;
}
public void addFile(File file) {
File tempFile = this.fileMap.get(file.getFileName());
if (null != tempFile) {
System.out.println("檔案《" + file.getFileName() + "》已存在,添加失敗...");
} else {
if (this.vacantVolume > file.getFileSize()) {
this.fileMap.put(file.getFileName(), file);
this.vacantVolume = this.vacantVolume - file.getFileSize();
System.out.println("添加檔案《" + file.getFileName() + "》成功...");
} else {
System.out.println("剩餘存儲空間不足,檔案《" + file.getFileName()
+ "》添加失敗...");
}
}
}
public void deleteFile(String fileName) {
File tempFile = this.fileMap.get(fileName);
if (null == tempFile) {
System.out.println("檔案《" + fileName + "》不存在,删除失敗...");
} else {
this.fileMap.remove(fileName);
this.vacantVolume = this.vacantVolume + tempFile.getFileSize();
System.out.println("删除檔案《" + fileName + "》成功...");
}
}
public void listFiles() {
if (fileMap.size() > 0) {
System.out.println();
System.out
.println("*********************檔案清單*********************");
int i = 1;
for (Entry<String, File> entry : fileMap.entrySet()) {
System.out.println(i + ". 檔案名:《"
+ entry.getValue().getFileName() + "》,檔案大小:"
+ entry.getValue().getFileSize() + "兆。");
i++;
}
System.out.println();
}
}
}
接下來定義一個USB儲存設備的接口類 USBDevice,其聲明的方法如下。
import java.util.List;
public interface USBDevice {
//傳回USB儲存設備的總容量
public Double getUSBVolume();
//傳回USB儲存設備的剩餘可用空間
public Double getUSBVacantVolume();
//列出USB儲存設備中的檔案資訊
public void listUSBFiles();
//添加檔案到USB儲存設備
public void addToUSB(File file);
//從USB儲存設備删除單個檔案
public void deleteFromUSB(String fileName);
//從USB儲存設備批量删除檔案
public void deleteFromUSB(List<String> fileNames);
}
接下來該定義我們的擴充卡類了,該類名為 CardReader (讀卡器),它通過調用SD卡類中的方法實作了在 USBDevice 中聲明的接口方法,代碼如下。
import java.util.List;
public class CardReader implements USBDevice{
private SDCard sdCard;
public CardReader(SDCard sdCard) {
super();
this.sdCard = sdCard;
}
@Override
public Double getUSBVolume() {
return this.sdCard.getVolume();
}
@Override
public Double getUSBVacantVolume() {
return this.sdCard.getVacantVolume();
}
@Override
public void listUSBFiles() {
this.sdCard.listFiles();
}
@Override
public void addToUSB(File file) {
this.sdCard.addFile(file);
}
@Override
public void deleteFromUSB(String fileName) {
this.sdCard.deleteFile(fileName);
}
@Override
public void deleteFromUSB(List<String> fileNames) {
if(null!=fileNames && fileNames.size()>0){
for(String fileName:fileNames){
this.sdCard.deleteFile(fileName);
}
}
}
}
最後定義電腦 Computer 類,該類僅支援插入具有USB接口的裝置(即實作了 USBDevice 接口的對象),我們在此為其添加了一個 USBDevice 的内部屬性代表連接配接到電腦的USB裝置,在 Computer 類中建立一個Main方法進行測試。
import java.util.ArrayList;
public class Computer {
private USBDevice usbDevice;
private USBDevice getUsbDevice() {
return usbDevice;
}
private void setUsbDevice(USBDevice usbDevice) {
this.usbDevice = usbDevice;
}
public static void main(String[] args) {
// 建立一個大小為1024M的SD卡
SDCard sdCard = new SDCard(1024D);
// 在SD卡中存入兩個檔案
sdCard.addFile(new File("Java從入門到放棄.pdf", 10D));
sdCard.addFile(new File("不良人之靈主.avi", 68D));
// 拷入重複檔案測試
sdCard.addFile(new File("不良人之靈主.avi", 68D));
// 列出SD卡中檔案資訊
sdCard.listFiles();
// 接下來将SD卡插入到讀卡器
CardReader cardReader = new CardReader(sdCard);
Computer computer = new Computer();
// 再将讀卡器插入電腦
computer.setUsbDevice(cardReader);
computer.getUsbDevice().addToUSB(new File("唐伯虎點秋香.rmvb", 600D));
computer.getUsbDevice().addToUSB(new File("C++基礎算法.txt", 0.01D));
computer.getUsbDevice().addToUSB(new File("冰河世紀4.rmvb", 888D));
computer.getUsbDevice().listUSBFiles();
computer.getUsbDevice().deleteFromUSB("Java從入門到放棄.pdf");
computer.getUsbDevice().listUSBFiles();
computer.getUsbDevice().deleteFromUSB(new ArrayList<String>() {
{
add("Java從入門到放棄.pdf");
add("不良人之靈主.avi");
}
});
computer.getUsbDevice().listUSBFiles();
}
}
運作程式結果列印如下:
添加檔案《Java從入門到放棄.pdf》成功...
添加檔案《不良人之靈主.avi》成功...
檔案《不良人之靈主.avi》已存在,添加失敗...
*********************檔案清單*********************
1. 檔案名:《Java從入門到放棄.pdf》,檔案大小:10.0兆。
2. 檔案名:《不良人之靈主.avi》,檔案大小:68.0兆。
添加檔案《唐伯虎點秋香.rmvb》成功...
添加檔案《C++基礎算法.txt》成功...
剩餘存儲空間不足,檔案《冰河世紀4.rmvb》添加失敗...
*********************檔案清單*********************
1. 檔案名:《唐伯虎點秋香.rmvb》,檔案大小:600.0兆。
2. 檔案名:《Java從入門到放棄.pdf》,檔案大小:10.0兆。
3. 檔案名:《C++基礎算法.txt》,檔案大小:0.01兆。
4. 檔案名:《不良人之靈主.avi》,檔案大小:68.0兆。
删除檔案《Java從入門到放棄.pdf》成功...
*********************檔案清單*********************
1. 檔案名:《唐伯虎點秋香.rmvb》,檔案大小:600.0兆。
2. 檔案名:《C++基礎算法.txt》,檔案大小:0.01兆。
3. 檔案名:《不良人之靈主.avi》,檔案大小:68.0兆。
檔案《Java從入門到放棄.pdf》不存在,删除失敗...
删除檔案《不良人之靈主.avi》成功...
*********************檔案清單*********************
1. 檔案名:《唐伯虎點秋香.rmvb》,檔案大小:600.0兆。
2. 檔案名:《C++基礎算法.txt》,檔案大小:0.01兆。
5、擴充卡模式的選擇
通過使用擴充卡模式,我們可以達到以下目的:
1 複用現有的類,解決現有類和複用環境要求不一緻的問題。
2 将目标類和适配者類解耦,通過引入一個擴充卡類重用現有的适配者類,而無需修改原有代碼。
3 一個對象擴充卡可以把适配者類和它的子類都适配到目标接口。
類擴充卡模式和對象擴充卡模式比較:
-類擴充卡通過繼承方式來實作,是靜态的;而對象擴充卡通過組合方式來實作,是動态的。
-對于類擴充卡,擴充卡直接繼承自 Adaptee ,這使得擴充卡不能和 Adaptee 的子類一起工作。
-對于對象擴充卡,同一個擴充卡可以把 Adaptee 和它的子類都适配到目标接口。
-對于類擴充卡,擴充卡可以重定義 Adaptee 的部分行為,相當于子類覆寫父類的部分實作方法。
-對于對象擴充卡,要重定義 Adaptee 的行為比較困難,這種情況下,需要定義 Adaptee 的子類來實作重定義,然後讓擴充卡組合子類。
參考文章
GoF經典的著作《 Design Patterns: Elements of Reusable Object-Oriented Software 》(《設計模式:可複用面向對象軟體的基礎》)
《擴充卡模式原理及執行個體介紹》
《JDK中的設計模式之擴充卡模式》
《設計模式之預設适配模式》