如有错误或有补充,以及任何改进的意见,请留下您的高见
概念
适配器模式是一种结构型设计模式,它可以将一个类的接口转换成客户端所期望的另一个接口,从而使得原本由于接口不兼容而不能一起工作的两个类能够协同工作。
适配器模式中的角色包括目标接口(Target)、需要适配的类(Adaptee)和适配器(Adapter)。目标接口是客户端所期望的接口,需要适配的类是需要被适配的类或适配者类,适配器则是将需要适配的类的接口转换成目标接口的实现类。
源(Adaptee):这个角色通常是一个已经存在的类或者接口,其方法或行为与客户端所期望的不完全一致。源角色定义了需要被适配的接口,这些接口可能是由于技术限制、历史遗留问题或者第三方库等原因而无法直接满足客户端的需求。
适配器(Adapter):这个角色是适配器模式的核心,它负责将源角色的接口转换成目标角色所期望的接口。适配器通常通过继承源角色(类适配器模式)或者实现源角色的接口(对象适配器模式)来获取源角色的行为,并且同时实现目标角色的接口以满足客户端的需求。适配器可以在不修改源角色代码的情况下,对源角色的行为进行扩展或者修改,以符合目标角色的要求。
目标(Target):这个角色定义了客户端所期望的接口,客户端代码将直接与目标角色进行交互。目标角色的接口通常是根据客户端的需求来设计的,它可能是现有系统中的一个接口,也可能是为了满足新需求而定义的一个新接口。目标角色并不直接依赖于源角色,而是通过适配器来间接使用源角色的功能。
优点
提高了类的复用性:适配器模式将原本不兼容的类或接口组合在一起,使得原本由于接口不匹配而无法一起工作的类能够协同工作,提高了类的复用性。
降低了系统的耦合度:适配器模式将不同的类或接口组合在一起,使得原本紧耦合的两个类通过适配器解耦,降低了系统的耦合度。
提高了系统的可扩展性:通过使用适配器模式,可以在不修改原有类或接口的情况下,将新的类或接口加入到系统中,提高了系统的可扩展性。
缺点
增加了代码的复杂性:使用适配器模式需要设计适配器类,增加了代码的复杂性。
可能引入额外的性能开销:由于需要将一个类的接口转换成客户端所期望的另一种接口,可能会引入额外的性能开销。
可能违反单一职责原则:适配器模式可能会让一个类承担多个职责,违反了单一职责原则。
示例
现有一个AdvancedMediaPlayer可以播放MP4,MP3,但是客户希望能够用一个方法同时于是我们有了一个Target。
目标(Target)因为我们都是面向接口编程,所以客户端使用的应当是MediaPlayer接口的play方法。方便后期的维护。
我们有了目标(Target),则写一个类实现目标(Target)的接口,实现实现播放MP3,MP4这两种文件。
Client.java
public class Client { public static void main(String[] args) { // 创建AdvancedMediaPlayer的实例 AdvancedMediaPlayer advancedMediaPlayer = new AdvancedMediaPlayer(); // 创建适配器实例,将AdvancedMediaPlayer实例传入适配器构造函数中 MediaPlayer mediaPlayer = new MediaPlayerAdapter(advancedMediaPlayer); // 调用适配器的方法,实际上调用的是AdvancedMediaPlayer的方法 mediaPlayer.play("video", "movie.mp4"); // 输出: Playing video: movie.mp4 // 这里要放.MP3文件,原来的MediaPlayer是无法完成的,所以使用适配器来处理MP3 mediaPlayer.play("audio", "song.mp3"); // 输出: Playing audio: song.mp3 } } // 需要被适配的类(Adaptee) class AdvancedMediaPlayer { public void playVideo(String fileName) { System.out.println("Playing video: " + fileName); } public void playAudio(String fileName) { System.out.println("Playing audio: " + fileName); } } // 目标接口(Target) interface MediaPlayer { void play(String type, String fileName); } // 适配器(Adapter) class MediaPlayerAdapter implements MediaPlayer { private AdvancedMediaPlayer advancedMediaPlayer; public MediaPlayerAdapter(AdvancedMediaPlayer advancedMediaPlayer) { this.advancedMediaPlayer = advancedMediaPlayer; } @Override public void play(String type, String fileName) { if ("video".equalsIgnoreCase(type)) { // 调用AdvancedMediaPlayer的playVideo方法 advancedMediaPlayer.playVideo(fileName); } else if ("audio".equalsIgnoreCase(type)) { // 调用AdvancedMediaPlayer的playAudio方法 advancedMediaPlayer.playAudio(fileName); } else { throw new IllegalArgumentException("Invalid media type: " + type); } } }
ps:
那你问我说这个跟继承,实现有区别吗?
那我只能说有,但不完全有,继承主是要扩展旧的类,适配器是要重用旧的类。本质上差别不大。
而且,写一个接口,在接口的实现类中重用旧的类,不就是适配器模式了?
而且适配器模式调整更加方便,如果我们还有一个可以播放m3u8的视频的类存在,当客户端产生这个需求时,我们只需要再在适配器中整合一个就好。如果采用继承的方法,可以想象任务的完成并不会那么轻松。
适配器与继承的区别
- 目的与用途:
- 适配器模式:主要目的是解决接口不匹配的问题,使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。它属于结构型模式,通过转换接口,使原本不兼容的类能够协同工作。
- 继承:在面向对象编程中,通过继承,子类可以获得父类的所有属性和方法。继承的主要目的是代码重用和实现多态。
- 灵活性:
- 适配器模式:提供了更高的设计灵活性。通过适配器,你可以在不修改原有代码的情况下,将新的类或接口加入到系统中。
- 继承:通过继承,我们可以复用父类的代码,但一旦父类的代码发生变化,子类的代码也需要相应地调整。
- 耦合度与扩展性:
- 适配器模式:通过适配器,可以降低系统间的耦合度,并且提高系统的扩展性。因为适配器可以在不修改原有代码的情况下,将新的类或接口加入到系统中。
- 继承:在某些情况下,继承可能会增加代码的耦合度。例如,当父类的实现细节发生变化时,所有继承自该父类的子类都可能受到影响。
- 维护与升级:
- 适配器模式:由于适配器模式允许在运行时动态地改变对象间的接口,因此它提供了更好的维护性和升级能力。当系统中的某个接口发生变化时,只需要修改适配器即可,而不需要修改所有使用该接口的客户端代码。
- 继承:在继承关系中,当父类发生更改时,所有子类可能需要相应地调整。这可能会增加维护和升级的复杂性。
- 代码重用与复用:
- 适配器模式:通过适配器,可以复用旧类的代码,同时通过转换接口满足新的需求。适配器模式允许在不修改原有代码的情况下复用代码。
- 继承:继承本身就提供了代码重用的机制。子类可以直接使用父类的属性和方法,从而避免重复编写相同的代码。