揭秘设计模式:一个转接头,竟然能让你的代码更优雅?聊聊适配器模式

揭秘设计模式:一个转接头,竟然能让你的代码更优雅?聊聊适配器模式

你有没有过这样的经历?买了新的电脑,接口和旧显示器不兼容;或者出国旅行,手机充电器插不进当地的插座。这时候,一个转接头就能解决所有烦恼。

在软件开发的世界里,我们也经常遇到类似的问题:你手头有一个功能强大的类,却无法直接被你的新系统调用,因为它俩的接口“说不通”。你是否曾为此犯愁,是修改老代码,还是为新系统妥协?

别急,答案就在一个被誉为“代码界转接头”的设计模式——适配器模式Adapter Pattern里。它能让你在不改动任何老代码的前提下,让两个原本不兼容的接口,完美地协同工作。

什么是适配器模式?

简单来说,适配器模式就是将一个类的接口转换成客户希望的另一个接口,让原本因为接口不兼容而不能一起工作的类,能够和谐共处。

它包含三个核心角色:

  1. 目标接口 (Target): 这是我们希望使用的接口,也就是我们想要“适配”到的那个规范。
  2. 被适配者 (Adaptee): 这是那个“不兼容”的类,它就是我们想要利用但又无法直接使用的对象。
  3. 适配器 (Adapter): 这就是那个“转接头”,它实现了目标接口,但内部持有被适配者的实例,负责将调用者对目标接口的请求,转换成对被适配者方法的调用。

类图

为了更直观地理解这些类的关系,我们可以用一个类图来表示。
在这里插入图片描述

关系解析:

  • WavAdapter 实现了 MediaPlayer 接口。这表明 WavAdapter 能够被当作一个 MediaPlayer 来使用。
  • WavAdapter 持有一个 WavPlayer 的实例(通过 has a 关系)。这是适配器模式的核心,适配器通过这种方式来调用被适配者的方法。
  • AudioPlayer 使用 MediaPlayer 接口。这意味着 AudioPlayer 的代码只与 MediaPlayer 接口打交道,而不知道具体的实现类是 WavAdapter 还是其他播放器。

文末有编码参考


适配器模式在主流框架中的应用

理论听起来很棒,但在每天的业务开发中,适配器模式到底能帮我们解决什么问题呢?除了新老系统集成、对接第三方服务这类场景,其实你每天都在使用的Spring 框架中,适配器模式无处不在。

最经典的例子就是 Spring MVC 的处理器适配器。

我们都知道,Spring MVC 的核心是 DispatcherServlet。当一个 HTTP 请求进来时,它需要找到一个合适的处理器(Controller)来处理这个请求。但是,程序员写 Controller 的方式可能多种多样:

  • 有些 Controller 可能实现了特定的接口,比如 Controller
  • 有些 Controller 使用了 @Controller@RequestMapping 注解。
  • 还有些可能使用其他自定义的处理器。

如果 DispatcherServlet 要兼容所有这些不同的处理器,它的代码会变得非常复杂,而且每当有新的处理器类型出现,都需要修改它的核心代码。

Spring 的解决方案就是使用处理器适配器 (HandlerAdapter)

  • 目标接口 (Target): Spring 定义了一个 HandlerAdapter 接口,它只有一个核心方法 handle()DispatcherServlet 只与这个接口打交道。
  • 被适配者 (Adaptee): 就是我们自己编写的各种类型的 Controller。
  • 适配器 (Adapter): Spring 提供了多种适配器实现。例如,SimpleControllerHandlerAdapter 用来处理实现了 Controller 接口的处理器;RequestMappingHandlerAdapter 用来处理那些使用了 @RequestMapping 注解的处理器。

DispatcherServlet 收到请求后,会遍历所有的 HandlerAdapter,询问它们“你能不能处理这个请求对应的处理器?”一旦找到合适的适配器,它就会将请求“转接”给这个适配器,由适配器去调用我们 Controller 中的方法。

这个设计完美地体现了适配器模式的精髓:将不兼容的接口(各种类型的 Controller)转换成统一的接口(HandlerAdapter 。这使得 Spring 的核心代码 DispatcherServlet 变得极其简单和灵活,可以轻松支持各种新的处理器类型,而无需修改其内部实现。

java编码

为了更好地理解,我们来看一个具体的例子。

假设我们正在开发一个音频播放器,目前只支持 MP3 格式。我们有一个 MediaPlayer 接口,定义了统一的播放规范。

// 目标接口
public interface MediaPlayer {
    void play(String audioType, String fileName);
}

现在,我们拿到了一个新需求,需要播放 WAV 文件。我们有一个现成的 WavPlayer 类,但它的接口和我们的 MediaPlayer 不兼容。

// 被适配者:WavPlayer 有自己的播放方法
public class WavPlayer {
    public void playWav(String fileName) {
        System.out.println("Playing WAV file: " + fileName);
    }
}

为了让我们的主程序 AudioPlayer 能够像播放 MP3 一样播放 WAV,我们不能直接修改 WavPlayerMediaPlayer。最好的做法,就是创建一个“转接头”——WavAdapter

这个适配器需要做两件事:

  1. 实现我们的 MediaPlayer 接口,以便让 AudioPlayer 能够识别它。
  2. 持有一个 WavPlayer 实例,以便在需要时调用它的 playWav 方法。

这就是我们的适配器代码:

// 适配器
public class WavAdapter implements MediaPlayer {

    private WavPlayer wavPlayer;

    public WavAdapter(WavPlayer wavPlayer) {
        this.wavPlayer = wavPlayer;
    }

    @Override
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("wav")) {
            // 在这里,适配器将 play 方法的调用,转换成了对 playWav 的调用
            this.wavPlayer.playWav(fileName);
        } else {
            System.out.println("Invalid audio type: " + audioType);
        }
    }
}

现在,我们的主程序 AudioPlayer 就可以像下面这样使用了。它只关心 MediaPlayer 这个接口,而将具体的适配工作交给了 WavAdapter

public class AudioPlayer {
    public void play(String audioType, String fileName) {
        if (audioType.equalsIgnoreCase("mp3")) {
            System.out.println("Playing MP3 file: " + fileName);
        } else if (audioType.equalsIgnoreCase("wav")) {
            // 通过适配器,我们像播放MP3一样播放WAV
            MediaPlayer mediaPlayer = new WavAdapter(new WavPlayer());
            mediaPlayer.play(audioType, fileName);
        } else {
            System.out.println("Invalid audio type: " + audioType);
        }
    }

    public static void main(String[] args) {
        AudioPlayer audioPlayer = new AudioPlayer();
        audioPlayer.play("mp3", "beyond_the_horizon.mp3");
        audioPlayer.play("wav", "my_new_song.wav");
    }
}
适配器模式的真正价值

适配器模式的真正力量在于,它提供了一种优雅的方式来处理接口不兼容的问题,让我们能够:

  • 实现开闭原则: 当需要支持新的服务时,我们只需要新增一个适配器,而不需要修改任何已有的核心代码。这使得系统对扩展是开放的,对修改是封闭的。
  • 实现责任分离: 每个类都只做自己的事。WavPlayer 只管播放 WAV,AudioPlayer 只管调用 MediaPlayer 接口。而适配器,则只负责接口的转换。这让代码结构清晰,易于维护。
  • 新老系统无缝对接: 它让那些原本无法协作的模块,能够通过一个“转接头”和谐共处。

所以,下次当你面对两个不兼容的接口时,不妨停下来想一想:我是否需要一个“转接头”来让它们握手言和?

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ytadpole

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值