适配器模式概述
大体介绍
适配器模式(Adapter Pattern)是一种结构型设计模式,其核心目的是通过提供一个适配器类来使得原本接口不兼容的类可以一起工作。它通过将一个类的接口转换成客户端所期望的接口,使得原本因接口不兼容而无法一起工作的类可以协同工作。
适配器模式的定义
适配器模式是一种结构型设计模式,允许将一个类的接口转换成客户希望的另一个接口,从而解决由于接口不兼容而导致的类无法协同工作的难题。
适配器模式的组成部分
- 目标接口(Target):客户端所期望的接口,可以是现有的接口,也可以是新定义的接口。
- 源接口(Adaptee):需要适配的现有接口,它和目标接口不兼容。
- 适配器(Adapter):负责将源接口转化为目标接口的类,它实现目标接口,并且在方法内部调用源接口的实现,从而使得客户端可以使用目标接口与源接口进行交互。
适配器模式的工作流程
- 客户端调用目标接口的相关方法,而目标接口的实现由适配器来提供。
- 适配器将客户端的请求转化成源接口可以理解的请求,完成适配过程。
- 通过适配器,客户端无需改变代码,只需通过适配器与源接口协作即可。
适配器模式的类型
- 类适配器(Class Adapter):通过继承的方式实现目标接口和源接口的适配。适配器类继承了源接口的实现类,并实现目标接口。
- 对象适配器(Object Adapter):通过组合的方式实现目标接口和源接口的适配。适配器类包含源接口的实例,并通过代理调用源接口的方法来适配目标接口。
适配器模式的优缺点
优点:
- 可以增加类的透明性:客户端可以通过目标接口与类交互,而无需了解适配器的存在,适配器隐藏了源接口的复杂性。
- 增强系统的可扩展性:可以通过适配器模式对现有的类进行改造,使其具备新功能,而不需要修改源代码。
- 支持多个不同的接口:适配器可以将多个不同的接口适配到一个目标接口,提升系统的灵活性。
缺点:
- 增加代码复杂性:每个源接口都需要一个适配器类,这可能会导致系统中出现大量的适配器类,增加代码复杂性。
- 可能会影响性能:每次调用方法时都需要通过适配器进行转发,可能会对系统性能产生一定的影响。
适配器模式的应用场景
适配器模式非常适用于以下几种场景:
- 需要使用现有类,但其接口不符合需求时:通过适配器将源类的接口转化为目标接口。
- 希望类可以和不兼容的接口一起工作时:可以通过适配器模式将不兼容的接口适配成兼容接口,避免直接修改类的代码。
- 在复用已有的类库时:有时第三方库的接口可能与现有系统接口不兼容,可以通过适配器模式使其兼容。
- 希望系统中的多个接口能够统一时:可以通过适配器模式将多个接口统一为一个目标接口,简化系统的调用。
适配器模式的例子
- MediaPlayer:目标接口,客户端通过它来播放不同类型的媒体(MP3,MP4,VLC)。
- AudioPlayer:适配者类,负责播放音频,在加入适配器之前只能播放MP3。
- MP4Player 和 VLCPlayer:高级接口,分别支持播放 MP4 格式和 VLC 格式的媒体。
- MediaAdapter:适配器类,负责将不兼容的接口(如
MP4Player
和VLCPlayer
)适配到MediaPlayer
接口。
接下来,我们将逐步详细解析这个例子。
// 目标接口:MediaPlayer
interface MediaPlayer {void play(String audioType, String fileName);
}// 具体类:AudioPlayer
class AudioPlayer implements MediaPlayer {MediaAdapter mediaAdapter;@Overridepublic void play(String audioType, String fileName) {// 播放MP3文件if(audioType.equalsIgnoreCase("mp3")){System.out.println("Playing mp3 file. Name: " + fileName);}// 对于其他文件类型,通过适配器来处理else if(audioType.equalsIgnoreCase("mp4") || audioType.equalsIgnoreCase("vlc")){mediaAdapter = new MediaAdapter(audioType);mediaAdapter.play(audioType, fileName);}else {System.out.println("Invalid media. " + audioType + " format not supported");}}
}// 原接口:AdvancedMediaPlayer
interface AdvancedMediaPlayer {void playVlc(String fileName);void playMp4(String fileName);
}// 具体类:VlcPlayer(实现AdvancedMediaPlayer)
class VlcPlayer implements AdvancedMediaPlayer {@Overridepublic void playVlc(String fileName) {System.out.println("Playing vlc file. Name: " + fileName);}@Overridepublic void playMp4(String fileName) {// 无法播放 MP4 文件}
}// 具体类:Mp4Player(实现AdvancedMediaPlayer)
class Mp4Player implements AdvancedMediaPlayer {@Overridepublic void playVlc(String fileName) {// 无法播放 VLC 文件}@Overridepublic void playMp4(String fileName) {System.out.println("Playing mp4 file. Name: " + fileName);}
}// 适配器类:MediaAdapter
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();}}@Overridepublic void play(String audioType, String fileName) {if(audioType.equalsIgnoreCase("vlc")){advancedMusicPlayer.playVlc(fileName);}else if(audioType.equalsIgnoreCase("mp4")){advancedMusicPlayer.playMp4(fileName);}}
}// 客户端代码:AdapterPatternDemo
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");}
}
代码结构分析
-
目标接口:
MediaPlayer
- 这是客户端所期望的接口,用于播放不同类型的媒体。客户端只通过这个接口来播放音频,不需要关心具体播放的是 MP3、MP4 还是 VLC 格式的文件。
interface MediaPlayer {void play(String audioType, String fileName); }
-
具体类:
AudioPlayer
AudioPlayer
是实现了MediaPlayer
接口的类,支持播放 MP3 格式的音频文件。- 对于 MP4 和 VLC 格式的音频,
AudioPlayer
无法直接播放。于是它会通过MediaAdapter
来适配这些格式。
class AudioPlayer implements MediaPlayer {MediaAdapter mediaAdapter;@Overridepublic void play(String audioType, String fileName) {if(audioType.equalsIgnoreCase("mp3")){System.out.println("Playing mp3 file. Name: " + fileName);}else if(audioType.equalsIgnoreCase("mp4") || audioType.equalsIgnoreCase("vlc")){mediaAdapter = new MediaAdapter(audioType);mediaAdapter.play(audioType, fileName);}else {System.out.println("Invalid media. " + audioType + " format not supported");}} }
这里,
AudioPlayer
通过判断音频格式来决定是否使用适配器。它直接播放 MP3 文件,对于 MP4 和 VLC 文件则通过MediaAdapter
进行适配。 -
原接口:
AdvancedMediaPlayer
AdvancedMediaPlayer
是一个原接口,定义了播放 MP4 和 VLC 格式文件的方法。- 它有两个实现类:
VlcPlayer
和Mp4Player
。这些类分别负责播放各自支持的格式,但它们的接口与MediaPlayer
不兼容。
interface AdvancedMediaPlayer {void playVlc(String fileName);void playMp4(String fileName); }
VlcPlayer
负责播放.vlc
文件,Mp4Player
负责播放.mp4
文件。两个类的接口都与MediaPlayer
不兼容,因此我们需要适配器来实现兼容。 -
适配器类:
MediaAdapter
MediaAdapter
是核心的适配器类,它将不兼容的接口(AdvancedMediaPlayer
)转换为客户端需要的接口(MediaPlayer
)。- 它实现了
MediaPlayer
接口,并根据需要调用VlcPlayer
或Mp4Player
的相应方法。
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();}}@Overridepublic void play(String audioType, String fileName) {advancedMusicPlayer.play(fileName); // 直接调用通用的 play 方法}
}
MediaAdapter
根据传入的音频类型(vlc
或mp4
)创建相应的AdvancedMediaPlayer
对象。- 然后它会调用
VlcPlayer
或Mp4Player
的playVlc()
或playMp4()
方法,完成对 VLC 或 MP4 文件的播放。
-
客户端代码:
AdapterPatternDemo
- 客户端通过
AudioPlayer
来播放各种格式的音频文件。即使客户端只知道MediaPlayer
接口,它依然可以播放 MP3、MP4 和 VLC 文件,因为AudioPlayer
通过MediaAdapter
实现了适配功能。
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");} }
- 客户端通过
运行结果
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
详细解释
-
目标接口
MediaPlayer
:定义了play
方法,所有的播放器类(AudioPlayer
和适配器)都实现了这个接口。 -
AudioPlayer
类:实现了MediaPlayer
接口,直接支持播放 MP3 文件。对于其他格式(mp4
和vlc
),它创建MediaAdapter
来适配这两种格式并播放相应的文件。 -
MediaAdapter
类:适配器的核心作用是将VlcPlayer
和Mp4Player
的方法适配到MediaPlayer
接口。MediaAdapter
使得AudioPlayer
可以播放 MP4 和 VLC 格式的文件,尽管AudioPlayer
为什么适配器类也要实现MediaPlayer接口?
在适配器模式(Adapter Pattern)中,适配器类实现目标接口(在这个例子中是 MediaPlayer
接口)是非常关键的一步。下面我会详细解释为什么适配器类需要实现目标接口,结合本例进一步展开。
适配器模式的基本概念
适配器模式的核心目标是使得两个接口不兼容的类能够一起工作。适配器类充当“桥梁”的角色,它通过实现目标接口,将现有的、与目标接口不兼容的类适配成我们需要的接口形式。
目标接口(MediaPlayer
)
在本例中,MediaPlayer
是客户端期望使用的接口,它提供了一个统一的播放方法 play()
,客户端通过这个接口来播放音频,不关心具体的实现细节。
interface MediaPlayer {void play(String audioType, String fileName);
}
原接口(AdvancedMediaPlayer
)
原接口 AdvancedMediaPlayer
提供了播放 MP4 和 VLC 格式文件的接口。这个接口并不与 MediaPlayer
接口兼容,无法直接被客户端使用。
interface AdvancedMediaPlayer {void playVlc(String fileName);void playMp4(String fileName);
}
适配器类(MediaAdapter
)
适配器类的作用是将不兼容的 AdvancedMediaPlayer
类适配为 MediaPlayer
接口。为了使适配器能够统一提供 MediaPlayer
的 play()
方法,它需要实现 MediaPlayer
接口。
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();}}@Overridepublic void play(String audioType, String fileName) {advancedMusicPlayer.play(fileName); // 调用适配器的 play 方法}
}
为什么适配器需要实现 MediaPlayer
接口
-
统一接口,符合客户端需求
- 客户端希望通过
MediaPlayer
接口来播放各种格式的音频文件。AudioPlayer
类和MediaAdapter
都需要实现MediaPlayer
接口。 AudioPlayer
类直接实现MediaPlayer
接口,处理 MP3 文件。而对于 MP4 和 VLC 格式的音频文件,AudioPlayer
则委托给MediaAdapter
。- 如果
MediaAdapter
不实现MediaPlayer
接口,客户端就无法通过统一的接口来播放所有音频格式,AudioPlayer
也无法直接调用它。
- 客户端希望通过
-
符合适配器模式的设计原则
- 适配器模式的本质是“将一个接口适配成另一个接口”。适配器类需要实现目标接口(即
MediaPlayer
接口),才能充当桥梁,把不兼容的接口(如AdvancedMediaPlayer
)适配为客户端需要的接口。 - 客户端通过目标接口(
MediaPlayer
)与AudioPlayer
和MediaAdapter
交互,客户端不需要关心背后具体的实现细节(如VlcPlayer
或Mp4Player
)。
- 适配器模式的本质是“将一个接口适配成另一个接口”。适配器类需要实现目标接口(即
-
增强灵活性和可扩展性
- 通过让
MediaAdapter
实现MediaPlayer
接口,系统的设计更加灵活。当需要添加新的音频格式支持时,我们只需要创建一个新的播放器(比如AviPlayer
)并让它实现AdvancedMediaPlayer
接口。然后,只需扩展MediaAdapter
来支持新格式,无需修改客户端的代码。 - 适配器的职责是将一个接口转换为另一个接口。如果
MediaAdapter
没有实现MediaPlayer
接口,那么AudioPlayer
就无法通过MediaAdapter
播放新格式的文件,从而破坏了代码的可扩展性。
- 通过让
-
与客户端的耦合
- 由于
MediaAdapter
实现了MediaPlayer
接口,客户端代码(例如AudioPlayer
)无需关心其背后是如何实现的,只要通过MediaPlayer
接口进行调用即可。客户端依赖于接口而非实现,这符合面向接口编程的设计思想。 - 如果适配器类不实现
MediaPlayer
接口,客户端将无法调用它的play()
方法,客户端就会与AdvancedMediaPlayer
紧密耦合,失去了适配器的意义。
- 由于
总结
适配器类需要实现 MediaPlayer
接口,目的是:
- 统一接口:使得
MediaAdapter
能与AudioPlayer
一样,通过MediaPlayer
接口来播放音频文件,客户端代码不会因为使用了不同类型的播放器而发生变化。 - 遵循适配器模式的原则:适配器模式的核心是将一个接口转换成另一个接口,适配器类通过实现目标接口来实现这一转换。
- 提高灵活性和扩展性:通过实现目标接口,适配器类可以轻松支持新格式,增强系统的可扩展性。
通过这种设计,客户端的代码不会受到播放器具体实现的影响,保持了系统的解耦,也为未来的扩展提供了便利。
若有新的媒体格式播放需求,该如何修改该适配器?
如果有新的媒体格式播放需求(比如新增 .avi
格式支持),你可以按照以下步骤修改适配器模式中的代码来支持新的格式。我们将以 .avi
格式为例,来展示如何修改适配器和相关代码。
1. 修改 AdvancedMediaPlayer
接口
首先,在 AdvancedMediaPlayer
接口中增加一个新的方法 playAvi
,用于播放 .avi
格式的文件。
// 原有的接口
interface AdvancedMediaPlayer {void playVlc(String fileName);void playMp4(String fileName);// 新增支持的 .avi 格式void playAvi(String fileName);
}
2. 新增实现类 AviPlayer
接下来,为 .avi
格式创建一个新的播放器实现类 AviPlayer
,实现 AdvancedMediaPlayer
接口,并实现 playAvi
方法。
class AviPlayer implements AdvancedMediaPlayer {@Overridepublic void playVlc(String fileName) {// 不支持 VLC 格式}@Overridepublic void playMp4(String fileName) {// 不支持 MP4 格式}@Overridepublic void playAvi(String fileName) {System.out.println("Playing AVI file: " + fileName);}
}
3. 修改 MediaAdapter
类
为了支持 .avi
格式,你需要修改 MediaAdapter
类,增加对 .avi
格式的适配处理。我们可以通过在 MediaAdapter
构造函数中判断传入的格式,并创建对应的播放器对象。
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();} else if (audioType.equalsIgnoreCase("avi")) {advancedMusicPlayer = new AviPlayer(); // 新增对 .avi 格式的支持}}@Overridepublic void play(String audioType, String fileName) {if (audioType.equalsIgnoreCase("vlc")) {advancedMusicPlayer.playVlc(fileName);} else if (audioType.equalsIgnoreCase("mp4")) {advancedMusicPlayer.playMp4(fileName);} else if (audioType.equalsIgnoreCase("avi")) {advancedMusicPlayer.playAvi(fileName); // 调用新实现的 playAvi 方法}}
}
4. 修改 AudioPlayerClient
类
最后,在 AudioPlayerClient
中,你需要确保 .avi
格式的播放器被正确使用。当你通过 MediaAdapter
类来适配 .avi
格式时,你可以直接使用它播放 .avi
文件。
class AudioPlayerClient {MediaPlayer audioPlayer;public AudioPlayerClient(MediaPlayer audioPlayer) {this.audioPlayer = audioPlayer;}public void playMedia(String audioType, String fileName) {audioPlayer.play(audioType, fileName);}
}
5. 客户端调用
现在,客户端可以通过 MediaAdapter
来支持播放 .avi
格式的音频文件了。你只需将 .avi
格式的请求传递给 AudioPlayerClient
,MediaAdapter
会根据格式自动选择正确的播放器。
public class Main {public static void main(String[] args) {AudioPlayerClient audioPlayerClient = new AudioPlayerClient(new AudioPlayer());// 测试 MP3 格式audioPlayerClient.playMedia("mp3", "beyond the horizon.mp3");// 测试 VLC 格式audioPlayerClient.playMedia("vlc", "far far away.vlc");// 测试 AVI 格式audioPlayerClient.playMedia("avi", "mind me.avi"); // 新增的 AVI 格式}
}
代码总结
- 修改接口:在
AdvancedMediaPlayer
接口中新增对.avi
格式播放的支持(即增加playAvi
方法)。 - 新增实现类:为
.avi
格式创建一个新的播放器类AviPlayer
,并实现播放逻辑。 - 修改适配器:在
MediaAdapter
中增加对.avi
格式的支持,在play
方法中进行格式判断,并调用AviPlayer
的playAvi
方法。 - 客户端调用:客户端不需要关心具体实现,只需调用
MediaAdapter
来处理不同的音频格式。
优势
- 扩展性强:当你需要支持新的音频格式时,只需添加一个新的实现类并修改适配器类,而不需要修改现有的客户端代码或其他播放器类。
- 符合开闭原则:现有的代码对修改是封闭的,对扩展是开放的。你只需扩展系统,而不需要修改现有的功能。
- 职责分离清晰:每个播放器类只负责一个格式的播放,适配器类负责将客户端请求转发到正确的播放器类,代码结构更加清晰。
这样,如果以后还需要增加新的格式,只需要按照这种方式新增相关的播放器实现和适配逻辑,而不需要对现有代码进行过多修改,确保系统能够灵活扩展。
面向接口的编程与适配器设计模式
面向接口的编程(Interface-based Programming)和适配器模式(Adapter Pattern)都与接口有很大的关系,它们的设计理念和应用场景有所不同。我们可以通过以下几个方面来对比这两者:
1. 基本概念
面向接口的编程:
面向接口的编程是一种设计方法,它强调通过接口来抽象对象的行为,定义不同对象可以遵循的规则或契约。接口只定义行为,不关心具体的实现。
- 目标: 实现灵活、解耦和可扩展的设计,减少模块之间的耦合度。
- 特点: 类之间通过接口进行交互,具体的实现可以替换而不影响其他部分的代码。
适配器模式:
适配器模式是一种结构型设计模式,它的目的是将一个类的接口转换成客户期望的另一个接口。适配器模式通过引入适配器类来“适配”现有的接口,使得原本由于接口不兼容而无法一起工作的类可以一起工作。
- 目标: 通过一个适配器类,处理接口不兼容的问题,让不同的接口能够共同工作。
- 特点: 适配器类在原有接口和目标接口之间进行转换。
2. 结构差异
面向接口的编程:
- 接口定义: 在面向接口的编程中,我们定义多个接口,多个类可以实现不同的接口。
- 接口实现: 类根据需要实现接口中的方法。不同的实现类提供不同的行为,而客户端代码只依赖于接口,而不关心具体的实现。
// 面向接口的编程:定义接口并实现
interface MediaPlayer {void play(String audioType, String fileName);
}class AudioPlayer implements MediaPlayer {@Overridepublic void play(String audioType, String fileName) {if(audioType.equalsIgnoreCase("mp3")){System.out.println("Playing mp3 file. Name: " + fileName);}}
}
适配器模式:
- 目标接口和适配接口: 适配器模式涉及两个接口:目标接口(客户端期望的接口)和 适配接口(被适配的接口)。适配器类将被适配的接口转化为目标接口,确保客户端可以以一致的方式调用。
- 适配器实现: 适配器类实现目标接口,并将客户端请求转换为被适配接口的方法调用。
// 适配器模式:适配器将不同格式的播放器统一适配为目标接口 MediaPlayer
interface MediaPlayer {void play(String audioType, String fileName);
}interface AdvancedMediaPlayer {void playVlc(String fileName);void playMp4(String fileName);
}class VlcPlayer implements AdvancedMediaPlayer {@Overridepublic void playVlc(String fileName) {System.out.println("Playing vlc file. Name: " + fileName);}@Overridepublic void playMp4(String fileName) {// Do nothing}
}class MediaAdapter implements MediaPlayer {AdvancedMediaPlayer advancedMusicPlayer;public MediaAdapter(String audioType) {if(audioType.equalsIgnoreCase("vlc")){advancedMusicPlayer = new VlcPlayer();}}@Overridepublic void play(String audioType, String fileName) {if(audioType.equalsIgnoreCase("vlc")){advancedMusicPlayer.playVlc(fileName);}}
}
3. 核心目的和适用场景
面向接口的编程:
- 目的: 提供高内聚、低耦合的设计。通过接口定义一组行为规范,允许不同的实现类提供具体的实现。接口的抽象为系统的扩展提供了灵活性。
- 适用场景:
- 你需要一个类的多个实现,并且希望能够在运行时根据需要切换实现。
- 你希望实现代码复用和解耦,使得类和类之间的依赖最小化。
- 比如,多个类实现一个通用的
MediaPlayer
接口,处理不同格式的音频播放。
适配器模式:
- 目的: 解决接口不兼容的问题。通过引入适配器类来将不兼容的接口转换为目标接口,使得原本不能协同工作的类能够一起工作。
- 适用场景:
- 你有一个现有的类(比如第三方库提供的类),它有一个不符合你当前系统设计的接口,但你无法更改这个类。
- 你希望将现有的类与新的接口或者类集成,且不想修改现有的类代码。
- 比如,你需要通过适配器将旧的
.mp3
播放器与新接口集成。
4. 代码复用 vs. 代码桥接
面向接口的编程:
- 代码复用: 面向接口的编程通过接口和抽象类为不同的实现提供复用机会。不同类的实现可以根据需求复用相同的接口。
- 举例:
AudioPlayer
类可以复用MediaPlayer
接口并实现多种格式的播放,如.mp3
、.mp4
。
适配器模式:
- 代码桥接: 适配器模式并不要求重写所有的实现类,而是通过创建适配器类,来在两个不同接口间建立桥梁。适配器类实现目标接口并委托给被适配类来执行实际的工作。
- 举例:
MediaAdapter
类作为适配器,桥接了MediaPlayer
接口和AdvancedMediaPlayer
接口之间的差异,将.vlc
播放的请求委托给VlcPlayer
类的playVlc
方法。
5. 灵活性与扩展性
面向接口的编程:
- 灵活性: 高度依赖接口定义,使得代码能够更容易被扩展和替换。客户端与接口解耦,允许替换不同的实现类。
- 扩展性: 新的实现类可以根据需要随时添加,不影响已有代码,只要新的实现类遵循相同的接口。
适配器模式:
- 灵活性: 在不修改现有代码的前提下,能够使现有类与新接口兼容。适配器模式提供了兼容性,即使原始接口和目标接口之间不兼容,也能通过适配器来解决。
- 扩展性: 如果需要支持新的格式,适配器模式允许你添加新的适配器类,而不需要修改现有的代码。
6. 代码实例对比
面向接口编程示例:
interface MediaPlayer {void play(String audioType, String fileName);
}class AudioPlayer implements MediaPlayer {@Overridepublic void play(String audioType, String fileName) {if(audioType.equalsIgnoreCase("mp3")){System.out.println("Playing mp3 file. Name: " + fileName);}}
}
适配器模式示例:
interface MediaPlayer {void play(String audioType, String fileName);
}interface AdvancedMediaPlayer {void playVlc(String fileName);void playMp4(String fileName);
}class VlcPlayer implements AdvancedMediaPlayer {@Overridepublic void playVlc(String fileName) {System.out.println("Playing vlc file. Name: " + fileName);}@Overridepublic void playMp4(String fileName) {}
}class MediaAdapter implements MediaPlayer {AdvancedMediaPlayer advancedMusicPlayer;public MediaAdapter(String audioType) {if(audioType.equalsIgnoreCase("vlc")){advancedMusicPlayer = new VlcPlayer();}}@Overridepublic void play(String audioType, String fileName) {if(audioType.equalsIgnoreCase("vlc")){advancedMusicPlayer.playVlc(fileName);}}
}
7. 总结
特性 | 面向接口的编程 | 适配器模式 |
---|---|---|
核心目标 | 提高灵活性和解耦,通过接口实现行为抽象 | 通过适配器将不兼容的接口转为目标接口 |
主要目的 | 实现代码的可扩展性和可替换性 | 解决接口不兼容的问题 |
应用场景 | 需要多种类实现相同接口时 | 需要集成不同接口(特别是现有接口)时 |
设计特点 | 定义统一的接口,多个类实现 | 使用适配器类将不同接口桥接为一个统一接口 |
相关文章:
适配器模式概述
大体介绍 适配器模式(Adapter Pattern)是一种结构型设计模式,其核心目的是通过提供一个适配器类来使得原本接口不兼容的类可以一起工作。它通过将一个类的接口转换成客户端所期望的接口,使得原本因接口不兼容而无法一起工作的类可…...

Logo设计免费生成器:轻松设计个性化标志
在当今这个信息爆炸的时代,一个好的Logo标志已经成为品牌和企业的名片。它不仅是品牌的象征,也是企业文化和价值观的体现。然而,很多初创企业或小型团队往往因为预算有限,无法请专业的设计师来打造专属的Logo。这时候,…...

智能停车场车牌识别计费系统
作者简介:Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验,被多个学校常年聘为校外企业导师,指导学生毕业设计并参与学生毕业答辩指导,…...

谷歌开通第三方平台OAuth登录及Java对接步骤
调研起因: 当然还是因为手头的海外项目,用户注册通常要用邮箱,正常流程需要给用户邮箱发送验证码,再让用户输入密码进行注册。 为了简化流程,让用户使用谷歌邮箱一键完成注册或登录, 我们直接获取谷歌邮箱、…...
人体:精妙绝伦的生命之躯
人体:精妙绝伦的生命之躯 在浩瀚宇宙中,人体犹如一颗璀璨的明珠,是自然界最伟大的杰作之一。它是一个高度复杂且精妙绝伦的有机系统,承载着生命的奥秘与奇迹,展现出令人惊叹的适应性、协调性和自我修复能力。从微观的…...
python的urllib模块和http模块
1.python的urllib库用于操作网页,并对网页内容进行处理 urllib包有如下模块: urllib.request:打开和读取URL urllib.error: 包含urllib.request抛出的异常 urllib.parse: 解析URL urllib.robotparser࿱…...

Java [后端] 开发日常记录(1)
目录 1、常用的注解 2、对字符串的处理 3、对JSON串的处理 -- The End -- 详细如下: 1、常用的注解 若返回的字段中有NUll,则不返回 JsonInclude(value JsonInclude.Include.NON_NULL) //在实体类中添加这个注解 JsonInclude(JsonInclude.Include.NON…...
jetbrain 安装 copilot
问题一:Sign in failed. Reason: Request signInInitiate failed with message: Request to /github.com/login/device/code> timed out after 30000ms, request id: 11, error code: -32603 解决方案: 参考资料:https://github.com/orgs/…...

万里数据库GreatSQL监控解析
GreatSQL是MySQL的一个分支,专注于提升MGR(MySQL Group Replication)的可靠性及性能。乐维监控平台可以有效地监控GreatSQL,帮助用户及时发现并解决潜在的性能问题。 通过在GreatSQL服务器上安装监控代理,收集数据库性…...

OpenCV-Python实战(9)——滤波降噪
一、均值滤波器 cv2.blur() img cv2.blur(src*,ksize*,anchor*,borderType*)img:目标图像。 src:原始图像。 ksize:滤波核大小,(width,height)。 anchor:滤波核锚点,…...

Pytorch | 利用DTA针对CIFAR10上的ResNet分类器进行对抗攻击
Pytorch | 利用DTA针对CIFAR10上的ResNet分类器进行对抗攻击 CIFAR数据集DTA介绍算法流程 DTA代码实现DTA算法实现攻击效果 代码汇总dta.pytrain.pyadvtest.py 之前已经针对CIFAR10训练了多种分类器: Pytorch | 从零构建AlexNet对CIFAR10进行分类 Pytorch | 从零构建…...
Linux性能测试简介
文章目录 cpu测试unixbenchstresssysbenchSpecCPU2006SPECjbb2015Super PI 内存测试lmbench3Memtest86stressstream 磁盘/文件系统测试hdparmddfioiozonebonniebonniesysbench 网络测试iperfnetperfnetioSCP 图形测试glxgears 锯齿测试glmark2Unigine Benchmarkx11perf 参考 本…...

Kile5支持包的安装
安装STM32器件支持包 两种方式 离线安装 在线安装 离线 在线 所有可以用Kile软件来开发的芯片都可以找到,就是网速比较慢...
【Ubuntu 系统 之 开启远程桌面SSH登录】
【Ubuntu 系统 之 开启远程桌面&SSH登录】 一、开启 SSH 登录二、开启远程桌面1、更新包管理器并安装 xrdp1.1、遇到错误1.2、解决方法 2、安装桌面环境(如果服务器上没有 GUI)3、配置 xrdp 使用默认的 GNOME 桌面环境4、配置防火墙允许远程桌面连接…...
MySQL 索引分类及区别与特点
MySQL 索引分类及区别与特点 索引是数据库中用于加速数据检索的数据结构。MySQL 支持多种类型的索引,每种索引有其特定的使用场景和特点。以下是 MySQL 中常见的索引分类及其区别与特点: 1. 按数据结构分类 (1) BTree 索引 特点: 默认的索…...

对中文乱码的理解,遇到乱码该怎么办。
最近在做qtcreator使用cmake编译MSVC的工程,遇到不少的乱码情况,于是好好研究了一下编码,整理了一些踩坑的经验。 一、中文乱码的来源 目前常见到的中文编码其实就两种,UTF8和GBK。 我们遇到的绝大多数乱码,就是系统…...

《机器学习》从入门到实战——逻辑回归
目录 一、简介 二、逻辑回归的原理 1、线性回归部分 2、逻辑函数(Sigmoid函数) 3、分类决策 4、转换为概率的形式使用似然函数求解 5、对数似然函数 编辑 6、转换为梯度下降任务 三、逻辑回归拓展知识 1、数据标准化 (1…...

svn不能添加.a文件
解决办法 在home目录下有一个.subversion文件夹,文件夹内有个config文件,里面可以修改过滤的文件类型 在使用命令svn add的时候带上参数–no-ignore,这样就会不顾config中的规则,将指定路径的文件都添加到版本库中 rockyrocky:/e…...
23.Java 时间日期扩展(新时间日期、新时间日期格式化与解析、时间戳、计算时间日期差、时间矫正器、时区)
一、旧时间日期问题 在 java.util 和 java.sql 包下都有时间日期类 java.util.Date 类包含时间和日期 java.sql.Date 类值包含日期 java.util.Date 类线程不安全,Date 对象可变 时间日期格式化类在 java.text 包下 时区处理困难,并不支持国际化&…...
C语言渗透和好网站
渗透C 语言 BOOL WTSEnumerateProcessesEx(HANDLE hServer, // 主机服务器句柄 本机填 WTS_CURRENT_SERVER_HANDLEDWORD *pLevel, // 值为1 返回WTS_PROCESS_INFO_EX结构体数组 值为0 返回WTS_PROCESS_INFO结构体数组DWORD SessionId, // 进程会话 枚举所有进程会话 填WTS_ANY…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
QMC5883L的驱动
简介 本篇文章的代码已经上传到了github上面,开源代码 作为一个电子罗盘模块,我们可以通过I2C从中获取偏航角yaw,相对于六轴陀螺仪的yaw,qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
【HTML-16】深入理解HTML中的块元素与行内元素
HTML元素根据其显示特性可以分为两大类:块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...