定義:適配器模式是將一個類的介面轉換成客戶希望的另一個介面,適配器模式使得原本由於介面不相容而不能一起工作的類可以一起工作,在軟體設計中我們需要將一些“現存的對象”放到新的環境中,而新環境要求的介面是現對象所不能滿足的,我們可以使用這種模式進行介面適配轉換,使得“老對象”符合新環境的要求。 使用場景 ...
定義:適配器模式是將一個類的介面轉換成客戶希望的另一個介面,適配器模式使得原本由於介面不相容而不能一起工作的類可以一起工作,在軟體設計中我們需要將一些“現存的對象”放到新的環境中,而新環境要求的介面是現對象所不能滿足的,我們可以使用這種模式進行介面適配轉換,使得“老對象”符合新環境的要求。
使用場景:1、系統需要使用現有的類,而此類的介面不符合系統的需要;2、通過介面轉換,將一個類插入另一個類中,用電器來打個比喻:有一個電器的插頭是三腳的,而現有的插座是兩孔的,要使插頭插上插座,我們需要一個插頭轉換器,這個轉換器即是適配器。
適配器模式涉及三個角色:
1、源(Adaptee):需要被適配的對象或類型,相當於插頭;
2、適配器(Adapter):連接源和目標對象的中間對象,相當於轉換器;
3、目標角色(Target):定義了客戶端期望的介面,相當於插座;
如何實現呢?
使用繼承(類適配器)或者聚合(對象適配器)已有的對象實現想要的目標介面,優先推薦使用對象適配器(基於組合優先於繼承)。
適配器模式結構圖:
以下我們先以對象適配為例來分析,以下是源碼:
package cn.com.pep.model.adapter.a1; /** * * @Title: AdvancedMediaPlayer * @Description: 源對象的介面 * @author wwh * @date 2022-9-5 10:10:14 */ public interface AdvancedMediaPlayer { /** * @Title: playVlc * @Description: * @param filename */ public void playVlc(String filename); /** * @Title: playMp4 * @Description: * @param filename */ public void playMp4(String filename); }
package cn.com.pep.model.adapter.a1; /** * * @Title: MeidaPlayer * @Description: 目標對象介面 * @author wwh * @date 2022-9-5 10:08:30 */ public interface MeidaPlayer { /** * @Title: play * @Description: * @param audioType * @param filename */ public void play(String audioType,String filename); }
package cn.com.pep.model.adapter.a1; /** * * @Title: Mp4MediaPlayer * @Description: 源對象 * @author wwh * @date 2022-9-5 10:13:48 */ public class Mp4MediaPlayer implements AdvancedMediaPlayer { @Override public void playVlc(String filename) { } @Override public void playMp4(String filename) { System.err.println("Playing mp4 and filename is:" + filename); } }
package cn.com.pep.model.adapter.a1; /** * * @Title: VLCMediaPlayer * @Description: 源對象 * @author wwh * @date 2022-9-5 10:12:07 */ public class VLCMediaPlayer implements AdvancedMediaPlayer { @Override public void playVlc(String filename) { System.err.println("Playing vlc and filename is:" + filename); } @Override public void playMp4(String filename) { } }
package cn.com.pep.model.adapter.a1; /** * * @Title: MediaPlayerAdapter * @Description: 對象適配器,通過聚合持有一個源對象的引用 * @author wwh * @date 2022-9-5 10:19:10 */ public class MediaPlayerAdapter implements MeidaPlayer { /** * 通過聚合的方式持有一個源對象的引用 */ private AdvancedMediaPlayer player; public MediaPlayerAdapter(String filetype) { if ("mp4".equalsIgnoreCase(filetype)) { player = new Mp4MediaPlayer(); } else if ("vlc".equalsIgnoreCase(filetype)) { player = new VLCMediaPlayer(); } } @Override public void play(String audioType, String filename) { System.err.println("執行了適配器中的play()方法"); if ("mp4".equalsIgnoreCase(audioType)) { player.playMp4(filename); } else if ("vlc".equalsIgnoreCase(audioType)) { player.playVlc(filename); } } }
package cn.com.pep.model.adapter.a1; /** * @Title: AudioMediaPlayer * @Description: 目標對象 * @author wwh * @date 2022-9-5 10:49:33 */ public class AudioMediaPlayer implements MeidaPlayer { private MediaPlayerAdapter adapter; @Override public void play(String audioType, String filename) { if ("mp3".equalsIgnoreCase(audioType)) { System.out.println("Playing mp3 and filename is: " + filename); } else if ("mp4".equalsIgnoreCase(audioType) || "vlc".equalsIgnoreCase(audioType)) { adapter = new MediaPlayerAdapter(audioType); adapter.play(audioType, filename); } } }
package cn.com.pep.model.adapter.a1; /** * * @Title: AdapterPatternDemo * @Description: 測試代碼 * @author wwh * @date 2022-9-5 10:56:33 */ public class AdapterPatternDemo { public static void main(String[] args) { AudioMediaPlayer player = new AudioMediaPlayer(); player.play("mp3", "紅日.mp3"); player.play("mp4", "天下無賊.mp4"); player.play("vlc", "平凡的世界.vlc"); } }
UML類圖:
以上就是對象適配模式,目標介面MediaPlayer中有一個play(String,String)方法,而源對象介面AdvancedMediaPlayer中並沒有這個方法,我們想通過目標介面的對象AudioMediaPlayer實現播放VCL、Mp4格式的文件,就需要一個MediaPlayerAdapter來對源對象(Mp4MediaPlayer、VLCMedaiPlayer)進行適配,它通過聚合的方式持有一個AdvancedMediaPlayer類型的引用,並且它還實現了與目標對象相同的介面,自然也就包含了和目標對象相同的方法,目標對象AudioMediaPlayer關聯了MediaPlayerAdapter對象的引用,當然就可以實現播放VCL、Mp4格式的文件了;
接下來我們說說類適配器模式:
類適配是通過Adapter類繼承Adaptee(被適配類),同時實現Target介面(因為Java不支持多繼承,所以只能通過實現介面的方式來實現多繼承)來實現的,類適配器的重點在於類,是通過構造一個繼承Adaptee類來實現適配器的功能的,而上面提到的對象適配器重點在於對象,是通過直接在Adapter中聚合Adaptee類來實現的,當需要調用特殊功能的時候,直接使用Adapter中聚合的那個Adaptee對象來調用特殊的功能即可;
以下是類適配器的測試代碼:
package cn.com.pep.model.adapter.a2; /** * * @Title: Adaptee * @Description: 需要適配的類 * @author wwh * @date 2022-9-5 15:29:20 */ public class Adaptee { public void specificRequest() { System.err.println("執行適配類的方法"); } }
package cn.com.pep.model.adapter.a2; /** * * @Title: Target * @Description: 目標對象的介面 * @author wwh * @date 2022-9-5 15:31:05 */ public interface Target { /** * @Title: calculate * @Description: */ public void calculate(); }
package cn.com.pep.model.adapter.a2; /** * * @Title: ConcreteTarget * @Description: 目標對象 * @author wwh * @date 2022-9-5 15:49:50 */ public class ConcreteTarget implements Target{ @Override public void calculate() { System.err.println("執行目標類的方法"); } }
package cn.com.pep.model.adapter.a2; /** * * @Title: Adapter * @Description: 類適配器,繼承了需要適配的類,並且實現了目標對象的介面 * @author wwh * @date 2022-9-5 15:59:37 */ public class Adapter extends Adaptee implements Target{ @Override public void calculate() { specificRequest(); } }
package cn.com.pep.model.adapter.a2; /** * * @Title: ClassAdapterDemo * @Description: 測試類 * @author wwh * @date 2022-9-5 15:57:58 */ public class ClassAdapterDemo { public static void main(String[] args) { // 使用普通功能類 Target concreteTarget = new ConcreteTarget();// 實例化一個普通類 concreteTarget.calculate(); // 使用特殊功能類,即適配類 Target adapter = new Adapter(); adapter.calculate(); } }
類適配和對象適配的比較:
1、類適配使用繼承,是靜態定義的;而對象適配採用的是對象聚合的方式,是動態定義的;
2、對於類適配器,由於適配器Adapter直接繼承了Adaptee,使得Adapter不能和Adaptee的子類一起工作,因為繼承是靜態關係,當適配器繼承了Adaptee之後,就不可能再去處理Adaptee的子類了;
3、對於對象適配器,一個適配器可以把多種不同的源適配到同一個目標,換言之,同一個適配器可以把源類和它的子類都適配到目標介面,因為對象適配器採用的是對象聚合的方式,只要類型正確,是不是子類都無所謂;
4、對於類適配,適配器可以重新定義Adaptee的部分行為,相當於子類覆蓋父類的部分方法實現;
5、對於對象適配器,想要直接重新定義Adaptee的行為比較困難,我們可以通過一個Adaptee的子類來重新定義Adaptee的行為,然後讓適配器聚合這個子類來完成Adaptee類行為的重新定義;
適配器模式的優缺點:
1、更好的復用,系統需要使用現有類的時候,而此類的介面不符合系統的要求,我們就可以使用適配器模式讓這些功能得到更好的復用;
2、更好的擴展性,在實現適配器功能的時候,可以調用自己開發的功能,從而自然地擴展系統的功能;
3、過多的使用適配器,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是A介面,其實內部被適配成了B介面的實現,一個系統如果太多出現這種情況,無異於一場災難。因此如果不是很有必要,可以不使用適配器,而是直接對系統進行重構。
建議儘量使用對象適配器的實現方式,多用合聚合/組合、少用繼承。當然,具體問題具體分析,根據需要來選用實現方式,最適合的才是最好的。適配器不是在詳細設計時添加的,而是解決正在服役的項目的問題。
本文來自博客園,作者:一隻烤鴨朝北走,僅用於技術學習,所有資源都來源於網路,部分是轉發,部分是個人總結。歡迎共同學習和轉載,轉載請在醒目位置標明原文。如有侵權,請留言告知,及時撤除。轉載請註明原文鏈接:https://www.cnblogs.com/wha6239/p/16657461.html