1 動機
- 在軟體開發中采用類似于電源擴充卡的設計和編碼技巧
-
通常情況下,用戶端可以通過目标類的接口通路它所提供的服務
有時,現有的類可以滿足客戶類的功能需要,但是它所提供的接口不一定是客戶類所期望的,這可能是因為現有類中方法名與目标類中定義的方法名不一緻等原因所導緻的。
-
在這種情況下,現有的接口需要轉化為客戶類期望的接口,這樣保證了對現有類的重用。
如果不進行這樣的轉化,客戶類就不能利用現有類所提供的功能,擴充卡模式可以完成這樣的轉化。
- 在擴充卡模式中可以定義一個包裝類,包裝不相容接口的對象,這個包裝類指的就是擴充卡(Adapter),它所包裝的對象就是适配者(Adaptee),即被适配的類。
- 擴充卡提供客戶類需要的接口,擴充卡的實作就是把客戶類的請求轉化為對适配者的相應接口的調用。也就是說:當客戶類調用擴充卡的方法時,在擴充卡類的内部将調用适配者類的方法,而這個過程對客戶類是透明的,客戶類并不直接通路适配者類。是以,擴充卡可以使由于接口不相容而不能互動的類可以一起工作
2 模式定義
擴充卡模式(Adapter Pattern) :将一個接口轉換成客戶希望的另一個接口,擴充卡模式使接口不相容的那些類可以一起工作,其别名為包裝器(Wrapper)。
擴充卡模式既可以作為類結構型模式,也可以作為對象結構型模式。
3 模式結構
- Target:目标抽象類
- Adapter:擴充卡類
- Adaptee:适配者類
- Client:客戶類
擴充卡模式有對象擴充卡和類擴充卡兩種實作:
3.1 對象擴充卡

類擴充卡
4 時序圖
5 代碼分析
實作
我們有一個 MediaPlayer 接口和一個實作了 MediaPlayer 接口的實體類 AudioPlayer。預設情況下,AudioPlayer 可以播放 mp3 格式的音頻檔案。
我們還有另一個接口 AdvancedMediaPlayer 和實作了 AdvancedMediaPlayer 接口的實體類。該類可以播放 vlc 和 mp4 格式的檔案。
我們想要讓 AudioPlayer 播放其他格式的音頻檔案。為了實作這個功能,我們需要建立一個實作了 MediaPlayer 接口的擴充卡類 MediaAdapter,并使用 AdvancedMediaPlayer 對象來播放所需的格式。
AudioPlayer 使用擴充卡類 MediaAdapter 傳遞所需的音頻類型,不需要知道能播放所需格式音頻的實際類。AdapterPatternDemo,我們的示範類使用 AudioPlayer 類來播放各種格式。
擴充卡模式的 UML 圖
步驟 1
為媒體播放器和更進階的媒體播放器建立接口。
MediaPlayer.java
public interface MediaPlayer {
public void play(String audioType, String fileName);
}
AdvancedMediaPlayer.java
public interface AdvancedMediaPlayer {
public void playVlc(String fileName);
public void playMp4(String fileName);
}
步驟 2
建立實作了 AdvancedMediaPlayer 接口的實體類
VlcPlayer.java
public class VlcPlayer implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName) {
System.out.println("Playing vlc file. Name: "+ fileName);
}
@Override
public void playMp4(String fileName) {
//什麼也不做
}
}
Mp4Player.java
public class Mp4Player implements AdvancedMediaPlayer{
@Override
public void playVlc(String fileName) {
//什麼也不做
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing mp4 file. Name: "+ fileName);
}
}
步驟 3
建立實作了 MediaPlayer 接口的擴充卡類。
MediaAdapter.java
public class MediaAdapter implements MediaPlayer {
AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType){
if(audioType.equalsIgnoreCase("vlc") ){
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if(audioType.equalsIgnoreCase("vlc")){
advancedMusicPlayer.playVlc(fileName);
}else if(audioType.equalsIgnoreCase("mp4")){
advancedMusicPlayer.playMp4(fileName);
}
}
}
步驟 4
建立實作了 MediaPlayer 接口的實體類。
AudioPlayer.java
public class AudioPlayer implements MediaPlayer {
MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
//播放 mp3 音樂檔案的内置支援
if(audioType.equalsIgnoreCase("mp3")){
System.out.println("Playing mp3 file. Name: "+ fileName);
}
//mediaAdapter 提供了播放其他檔案格式的支援
else if(audioType.equalsIgnoreCase("vlc")
|| audioType.equalsIgnoreCase("mp4")){
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
}
else{
System.out.println("Invalid media. "+
audioType + " format not supported");
}
}
}
步驟 5
使用 AudioPlayer 來播放不同類型的音頻格式。
AdapterPatternDemo.java
public class AdapterPatternDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "beyond the horizon.mp3");
audioPlayer.play("mp4", "alone.mp4");
audioPlayer.play("vlc", "far far away.vlc");
audioPlayer.play("avi", "mind me.avi");
}
}
步驟 6
驗證輸出。
Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name: alone.mp4
Playing vlc file. Name: far far away.vlc
Invalid media. avi format not supported
8 優點
- 将目标類和适配者類解耦,通過引入一個擴充卡類來重用現有的适配者類,而無須修改原有代碼。
- 增加了類的透明性和複用性,将具體的實作封裝在适配者類中,對于用戶端類來說是透明的,而且提高了适配者的複用性。
- 靈活性和擴充性都非常好,通過使用配置檔案,可以很友善地更換擴充卡,也可以在不修改原有代碼的基礎上增加新的擴充卡類,完全符合“開閉原則”。
類擴充卡模式還具有如下優點:
由于擴充卡類是适配者類的子類,是以可以在擴充卡類中置換一些适配者的方法,使得擴充卡的靈活性更強。
對象擴充卡模式還具有如下優點:
一個對象擴充卡可以把多個不同的适配者适配到同一個目标,也就是說,同一個擴充卡可以把适配者類和它的子類都适配到目标接口。
9 缺點
類擴充卡模式的缺點如下:
對于Java、C#等不支援多重繼承的語言,一次最多隻能适配一個适配者類,而且目标抽象類隻能為抽象類,不能為具體類,其使用有一定的局限性,不能将一個适配者類和它的子類都适配到目标接口。
對象擴充卡模式的缺點如下:
與類擴充卡模式相比,要想置換适配者類的方法就不容易。如果一定要置換掉适配者類的一個或多個方法,就隻好先做一個适配者類的子類,将适配者類的方法置換掉,然後再把适配者類的子類當做真正的适配者進行适配,實作過程較為複雜。
10 适用環境
在以下情況下可以使用擴充卡模式:
- 系統需要使用現有的類,而這些類的接口不符合系統的需要。
- 想要建立一個可以重複使用的類,用于與一些彼此之間沒有太大關聯的一些類,包括一些可能在将來引進的類一起工作。
11 模式應用
Sun公司在1996年公開了Java語言的資料庫連接配接工具JDBC,JDBC使得Java語言程式能夠與資料庫連接配接,并使用SQL語言來查詢和操作資料。JDBC給出一個用戶端通用的抽象接口,每一個具體資料庫引擎(如SQL Server、Oracle、MySQL等)的JDBC驅動軟體都是一個介于JDBC接口和資料庫引擎接口之間的擴充卡軟體。抽象的JDBC接口和各個資料庫引擎API之間都需要相應的擴充卡軟體,這就是為各個不同資料庫引擎準備的驅動程式。
12 模式擴充
認擴充卡模式(Default Adapter Pattern)或預設擴充卡模式
當不需要全部實作接口提供的方法時,可先設計一個抽象類實作接口,并為該接口中每個方法提供一個預設實作(空方法),那麼該抽象類的子類可有選擇地覆寫父類的某些方法來實作需求,它适用于一個接口不想使用其所有的方法的情況。是以也稱為單接口擴充卡模式。
13 總結
- 結構型模式描述如何将類或者對象結合在一起形成更大的結構。
- 擴充卡模式用于将一個接口轉換成客戶希望的另一個接口,擴充卡模式使接口不相容的那些類可以一起工作,其别名為包裝器。擴充卡模式既可以作為類結構型模式,也可以作為對象結構型模式。
- 擴充卡模式包含四個角色:目标抽象類定義客戶要用的特定領域的接口;擴充卡類可以調用另一個接口,作為一個轉換器,對适配者和抽象目标類進行适配,它是擴充卡模式的核心;适配者類是被适配的角色,它定義了一個已經存在的接口,這個接口需要适配;在客戶類中針對目标抽象類進行程式設計,調用在目标抽象類中定義的業務方法。
- 在類擴充卡模式中,擴充卡類實作了目标抽象類接口并繼承了适配者類,并在目标抽象類的實作方法中調用所繼承的适配者類的方法;在對象擴充卡模式中,擴充卡類繼承了目标抽象類并定義了一個适配者類的對象執行個體,在所繼承的目标抽象類方法中調用适配者類的相應業務方法。
- 擴充卡模式的主要優點是将目标類和适配者類解耦,增加了類的透明性和複用性,同時系統的靈活性和擴充性都非常好,更換擴充卡或者增加新的擴充卡都非常友善,符合“開閉原則”;類擴充卡模式的缺點是擴充卡類在很多程式設計語言中不能同時适配多個适配者類,對象擴充卡模式的缺點是很難置換适配者類的方法。
- 擴充卡模式适用情況包括:系統需要使用現有的類,而這些類的接口不符合系統的需要;想要建立一個可以重複使用的類,用于與一些彼此之間沒有太大關聯的一些類一起工作。