目錄
本文的結構如下:
- 什么是適配器模式
- 為什么要用該模式
- 模式的結構
- 代碼示例
- 優點和缺點
- 適用環境
- 模式應用
- 模式擴展
- 總結
一、前言
適配器其實在我們的生活中是非常常見的,比如說,有的國家的插座都是三孔的,而我們的手機大部分都是兩孔的,是沒辦法直接把充電器插到插座上,這時我們可以使用一個適配器,適配器本身是三孔的,它可以直接插到三孔的插頭上,適配器本身可以提供一個兩孔的插座,然后我們的手機充電器就可以插到適配器上了,這樣我們原本只能插到兩孔上的插頭就能用三孔的插座了。
二、什么是適配器模式
適配器模式(Adapter Pattern):將一個接口轉換成客戶希望的另一個接口,使接口不兼容的那些類可以一起工作,其別名為包裝器(Wrapper)。適配器模式既可以作為類結構型模式,也可以作為對象結構型模式。
三、為什么要用該模式
通常情況下,客戶端可以通過目標類的接口訪問它所提供的服務。有時,現有的類可以滿足客戶類的功能需要,但是它所提供的接口不一定是客戶類所期望的,這可能是因為現有類中方法名與目標類中定義的方法名不一致等原因所導致的。
在這種情況下,現有的接口需要轉化為客戶類期望的接口,這樣保證了對現有類的重用。如果不進行這樣的轉化,客戶類就不能利用現有類所提供的功能,適配器模式可以完成這樣的轉化。
在適配器模式中可以定義一個包裝類,包裝不兼容接口的對象,這個包裝類指的就是適配器(Adapter),它所包裝的對象就是適配者(Adaptee),即被適配的類。
適配器提供客戶類需要的接口,適配器的實現就是把客戶類的請求轉化為對適配者的相應接口的調用。也就是說:當客戶類調用適配器的方法時,在適配器類的內部將調用適配者類的方法,而這個過程對客戶類是透明的,客戶類并不直接訪問適配者類。因此,適配器可以使由于接口不兼容而不能交互的類可以一起工作。
四、模式的結構
在適配器模式中,我們通過增加一個新的適配器類來解決接口不兼容的問題,使得原本沒有任何關系的類可以協同工作。根據適配器類與適配者類的關系不同,適配器模式可分為對象適配器和類適配器兩種,在對象適配器模式中,適配器與適配者之間是關聯關系;在類適配器模式中,適配器與適配者之間是繼承(或實現)關系。
4.1、對象適配器模式UML
在對象適配器模式結構圖中包含如下幾個角色:
- Target(目標抽象類):目標抽象類定義客戶所需接口,可以是一個抽象類或接口,也可以是具體類。
- Adapter(適配器類):適配器可以調用另一個接口,作為一個轉換器,對Adaptee和Target進行適配,適配器類是適配器模式的核心,在對象適配器中,它通過實現Target(或者說繼承)并關聯一個Adaptee對象使二者產生聯系。
- Adaptee(適配者類):適配者即被適配的角色,它定義了一個已經存在的接口,這個接口需要適配,適配者類一般是一個具體類,包含了客戶希望使用的業務方法,在某些情況下可能沒有適配者類的源代碼。
根據對象適配器模式結構圖,在對象適配器中,客戶端需要調用request()方法,而適配者類Adaptee沒有該方法,但是它所提供的specificRequest()方法卻是客戶端所需要的。為了使客戶端能夠使用適配者類,需要提供一個包裝類Adapter,即適配器類。這個包裝類包裝了一個適配者的實例,從而將客戶端與適配者銜接起來,在適配器的request()方法中調用適配者的specificRequest()方法。因為適配器類與適配者類是關聯關系(也可稱之為委派關系),所以這種適配器模式稱為對象適配器模式。
典型的對象適配器代碼:
/**
*
* @author w1992wishes
* @created @2017年11月6日-下午2:31:12
*
*/
public interface Target {
void request();
}
public class Adaptee {
public void specificRequest() {
System.out.println("specificRequest");
}
}
public class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
4.2、類適配器模式UML
類適配器模式結構和對象適配器模式結構相差不大,只是不是通過組合的方式,而是通過繼承的方式來實現,在這里Adapter繼承Adaptee。
/**
*
* @author w1992wishes
* @created @2017年11月6日-下午2:31:17
*
*/
public class Adapter extends Adaptee implements Target {
public Adapter() {
}
@Override
public void request() {
specificRequest();
}
}
五、代碼示例
假設有一個MediaPlayer接口和一個實現了MediaPlayer接口的實體類AudioPlayer。默認情況下,AudioPlayer可以播放mp3格式的音頻文件。
我們還有另一個接口AdvancedMediaPlayer和實現了AdvancedMediaPlayer接口的實體類。該類可以播放vlc和mp4等格式的文件。
現在想要讓AudioPlayer播放其他格式的音頻文件。為了實現這個功能,可以使用適配器模式來復用AdvancedMediaPlayer中已有的功能。
5.1、兩個目標接口
public interface MediaPlayer {
public void play(String audioType, String fileName);
}
public interface AdvancedMediaPlayer {
public void playVlc(String fileName);
public void playMp4(String fileName);
}
5.2、AdvancedMediaPlayer接口的實體類
VlcPlayer
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
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);
}
}
5.3、MediaPlayer接口的適配器類
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);
}
}
}
5.4、MediaPlayer接口的實體類
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.5 AudioPlayer播放不同類型的音頻格式
public class AdapterPatternDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "盡頭.mp3");
audioPlayer.play("mp4", "戰狼2.mp4");
audioPlayer.play("vlc", "高等教育.vlc");
audioPlayer.play("avi", "羞羞的鐵拳.avi");
}
}
六、優點和缺點
6.1、優點
- 將目標類和適配者類解耦,通過引入一個適配器類來重用現有的適配者類,而無須修改原有代碼。
- 增加了類的透明性和復用性,將具體的實現封裝在適配者類中,對于客戶端類來說是透明的,而且提高了適配者的復用性。
- 靈活性和擴展性都非常好,通過使用配置文件,可以很方便地更換適配器,也可以在不修改原有代碼的基礎上增加新的適配器類,完全符合“開閉原則”。
類適配器模式還具有如下優點:
- 由于適配器類是適配者類的子類,因此可以在適配器類中置換一些適配者的方法,使得適配器的靈活性更強。
對象適配器模式還具有如下優點:
- 一個對象適配器可以把多個不同的適配者適配到同一個目標,也就是說,同一個適配器可以把適配者類和它的子類都適配到目標接口。
6.2、缺點
類適配器模式的缺點如下:
- 對于Java、C#等不支持多重繼承的語言,一次最多只能適配一個適配者類,而且目標抽象類只能為抽象類,不能為具體類,其使用有一定的局限性,不能將一個適配者類和它的子類都適配到目標接口。
對象適配器模式的缺點如下:
- 與類適配器模式相比,要想置換適配者類的方法就不容易。如果一定要置換掉適配者類的一個或多個方法,就只好先做一個適配者類的子類,將適配者類的方法置換掉,然后再把適配者類的子類當做真正的適配者進行適配,實現過程較為復雜。
七、適用環境
在以下情況下可以使用適配器模式:
- 系統需要使用現有的類,而這些類的接口不符合系統的需要。
- 想要建立一個可以重復使用的類,用于與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。
八、模式應用
Sun公司在1996年公開了Java語言的數據庫連接工具JDBC,JDBC使得Java語言程序能夠與數據庫連接,并使用SQL語言來查詢和操作數據。JDBC給出一個客戶端通用的抽象接口,每一個具體數據庫引擎(如SQL Server、Oracle、MySQL等)的JDBC驅動軟件都是一個介于JDBC接口和數據庫引擎接口之間的適配器軟件。抽象的JDBC接口和各個數據庫引擎API之間都需要相應的適配器軟件,這就是為各個不同數據庫引擎準備的驅動程序。
九、模式擴展
缺省適配器模式
當不需要全部實現接口提供的方法時,可先設計一個抽象類實現接口,并為該接口中每個方法提供一個默認實現(空方法),那么該抽象類的子類可有選擇地覆蓋父類的某些方法來實現需求,它適用于一個接口不想使用其所有的方法的情況。因此也稱為單接口適配器模式。
十、總結
- 適配器模式用于將一個接口轉換成客戶希望的另一個接口,適配器模式使接口不兼容的那些類可以一起工作,其別名為包裝器。適配器模式既可以作為類結構型模式,也可以作為對象結構型模式。
- 適配器模式包含四個角色:目標抽象類定義客戶要用的特定領域的接口;適配器類可以調用另一個接口,作為一個轉換器,對適配者和抽象目標類進行適配,它是適配器模式的核心;適配者類是被適配的角色,它定義了一個已經存在的接口,這個接口需要適配;在客戶類中針對目標抽象類進行編程,調用在目標抽象類中定義的業務方法。
- 在類適配器模式中,適配器類實現了目標抽象類接口并繼承了適配者類,并在目標抽象類的實現方法中調用所繼承的適配者類的方法;在對象適配器模式中,適配器類繼承了目標抽象類并定義了一個適配者類的對象實例,在所繼承的目標抽象類方法中調用適配者類的相應業務方法。
- 適配器模式的主要優點是將目標類和適配者類解耦,增加了類的透明性和復用性,同時系統的靈活性和擴展性都非常好,更換適配器或者增加新的適配器都非常方便,符合“開閉原則”;類適配器模式的缺點是適配器類在很多編程語言中不能同時適配多個適配者類,對象適配器模式的缺點是很難置換適配者類的方法。
- 適配器模式適用情況包括:系統需要使用現有的類,而這些類的接口不符合系統的需要;想要建立一個可以重復使用的類,用于與一些彼此之間沒有太大關聯的一些類一起工作。