二十三种模式-适配器模式

适配器模式(Adapter Pattern)是一种结构型设计模式,它允许将不兼容的接口转换成客户端期望的接口,从而使原本因接口不匹配而不能一起工作的类可以协同工作。以下是关于适配器模式的详细介绍:

一、定义及作用

  • 定义:适配器模式将一个类的接口转换成客户端所期望的另一种接口,使原本由于接口不兼容而不能一起工作的那些类可以一起工作。
  • 作用
    • 兼容性:解决了接口不兼容的问题,让原本无法协同工作的类能够一起工作,提高了系统的兼容性。
    • 复用性:可以重用一些现有的类,而无需修改它们的代码,从而提高了代码的复用性。
    • 灵活性:客户端调用的是适配器类,而适配器类可以根据需要调用被适配类的方法,这样可以在不修改客户端代码的情况下,灵活地更换被适配类。

二、实现方式

适配器模式主要有两种实现方式:类适配器和对象适配器。

(一)类适配器
  • 结构
    • 目标接口(Target):定义客户端所期望的接口,它声明了一些方法,客户端通过这些方法来调用适配器的功能。
    • 适配器类(Adapter):通过继承被适配类来实现目标接口。它将被适配类的接口转换为目标接口,内部持有被适配类的实例,并将目标接口的方法调用转发给被适配类的实例。
    • 被适配类(Adaptee):定义了需要适配的接口,它有一些有用的方法,但是接口与客户端期望的接口不一致。
  • 示例
    • 假设有一个旧的音频播放器类AudioPlayer,它只能播放MP3格式的音频文件,接口方法为playMp3(String fileName)。现在客户端希望它也能播放VLC格式的音频文件。

    • 我们可以创建一个适配器类AudioPlayerAdapter,它继承自AudioPlayer,同时实现一个新的接口AdvancedAudioPlayer,该接口有一个方法play(String audioType, String fileName)

    • AudioPlayerAdapter类中,重写play方法,当audioType为"vlc"时,先将VLC格式的音频文件转换为MP3格式(假设有一个转换方法convertVlcToMp3),然后再调用playMp3方法来播放转换后的MP3文件。

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

      // 被适配类
      class AudioPlayer {
      public void playMp3(String fileName) {
      System.out.println("Playing mp3 file. Name: " + fileName);
      }
      }

      // 适配器类
      class AudioPlayerAdapter extends AudioPlayer implements AdvancedAudioPlayer {
      @Override
      public void play(String audioType, String fileName) {
      if (audioType.equalsIgnoreCase("vlc")) {
      String convertedFile = convertVlcToMp3(fileName);
      playMp3(convertedFile);
      }
      }

      private String convertVlcToMp3(String fileName) {
          // 转换逻辑,这里简化处理
          return fileName.replace(".vlc", ".mp3");
      }
      

      }

(二)对象适配器
  • 结构
    • 目标接口(Target):与类适配器中的目标接口相同,定义客户端期望的接口。
    • 适配器类(Adapter):实现目标接口,并且通过组合的方式包含一个被适配类的实例。适配器类将目标接口的方法调用转发给被适配类的实例。
    • 被适配类(Adaptee):与类适配器中的被适配类相同,定义了需要适配的接口。
  • 示例
    • 还是以上面的音频播放器为例。适配器类AudioPlayerAdapter实现AdvancedAudioPlayer接口,同时在类中包含一个AudioPlayer类型的成员变量。

    • AudioPlayerAdapter的构造方法中,传入一个AudioPlayer实例。在play方法中,根据audioType判断,如果是VLC格式,先调用转换方法,然后通过成员变量调用AudioPlayerplayMp3方法。

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

      // 被适配类
      class AudioPlayer {
      public void playMp3(String fileName) {
      System.out.println("Playing mp3 file. Name: " + fileName);
      }
      }

      // 适配器类
      class AudioPlayerAdapter implements AdvancedAudioPlayer {
      private AudioPlayer audioPlayer;

      public AudioPlayerAdapter(AudioPlayer audioPlayer) {
          this.audioPlayer = audioPlayer;
      }
      
      @Override
      public void play(String audioType, String fileName) {
          if (audioType.equalsIgnoreCase("vlc")) {
              String convertedFile = convertVlcToMp3(fileName);
              audioPlayer.playMp3(convertedFile);
          }
      }
      
      private String convertVlcToMp3(String fileName) {
          // 转换逻辑,这里简化处理
          return fileName.replace(".vlc", ".mp3");
      }
      

      }

三、优缺点

  • 优点
    • 提高兼容性:可以将不兼容的接口转换为目标接口,让原本不能一起工作的类能够协同工作。
    • 提高复用性:可以重用一些现有的类,无需修改它们的代码,提高了代码的复用性。
    • 符合开闭原则:客户端调用的是适配器类,而适配器类可以根据需要调用被适配类的方法,可以在不修改客户端代码的情况下,灵活地更换被适配类。
  • 缺点
    • 类适配器的缺点:由于适配器类是通过继承被适配类来实现目标接口的,所以它只能适配一个被适配类,如果要适配多个被适配类,就需要创建多个适配器类,这会增加系统的复杂性。而且,类适配器使用了继承,这可能会违反一些面向对象的设计原则,如里氏替换原则等。
    • 对象适配器的缺点:对象适配器需要在适配器类中创建被适配类的实例,这可能会增加系统的开销。而且,如果被适配类的接口有变化,适配器类也需要相应地进行修改,这可能会增加维护的难度。

四、应用场景

  • 遗留代码的适配:当需要在新的系统中使用一些遗留代码,但遗留代码的接口与新系统的接口不兼容时,可以使用适配器模式来适配这些遗留代码。
  • 第三方库的适配:当需要使用第三方库,但第三方库的接口与系统的接口不兼容时,可以使用适配器模式来适配第三方库。
  • 不同类库之间的适配:当需要将两个不同类库中的类组合在一起使用,但它们的接口不兼容时,可以使用适配器模式来适配这两个类库中的类。

案例

一、支付接口适配的背景

假设一个电商平台需要支持多种支付方式。每种支付方式都有自己的接口,例如:

  • 支付宝支付接口AlipayService,它有方法payWithAlipay(String orderId, double amount)
  • 微信支付接口WechatPayService,它有方法payWithWechat(String orderId, double amount)
  • 银联支付接口UnionPayService,它有方法payWithUnionPay(String orderId, double amount)

这些支付接口虽然功能相似,但方法名和参数等细节可能有所不同,这就导致客户端代码在调用不同的支付方式时需要编写不同的代码,增加了代码的复杂性和维护难度。

二、使用适配器模式进行适配

为了解决这个问题,可以定义一个通用的支付接口(目标接口),然后为每种支付方式创建一个适配器类,将具体的支付接口(被适配接口)适配到通用的支付接口上。

(一)定义通用支付接口(目标接口)
public interface PaymentAdapter {
    void pay(String orderId, double amount);
}
(二)创建适配器类
  • 支付宝支付适配器

    public class AlipayAdapter implements PaymentAdapter {
    private AlipayService alipayService;

      public AlipayAdapter(AlipayService alipayService) {
          this.alipayService = alipayService;
      }
    
      @Override
      public void pay(String orderId, double amount) {
          alipayService.payWithAlipay(orderId, amount);
      }
    

    }

  • 微信支付适配器

    public class WechatPayAdapter implements PaymentAdapter {
    private WechatPayService wechatPayService;

      public WechatPayAdapter(WechatPayService wechatPayService) {
          this.wechatPayService = wechatPayService;
      }
    
      @Override
      public void pay(String orderId, double amount) {
          wechatPayService.payWithWechat(orderId, amount);
      }
    

    }

  • 银联支付适配器

    public class UnionPayAdapter implements PaymentAdapter {
    private UnionPayService unionPayService;

      public UnionPayAdapter(UnionPayService unionPayService) {
          this.unionPayService = unionPayService;
      }
    
      @Override
      public void pay(String orderId, double amount) {
          unionPayService.payWithUnionPay(orderId, amount);
      }
    

    }

(三)客户端调用

在客户端代码中,只需要使用通用的支付接口PaymentAdapter来调用支付方法,而不需要关心具体的支付方式。这样,客户端代码就可以统一地调用不同的支付方式,提高了代码的可维护性和可扩展性。

public class PaymentClient {
    public static void main(String[] args) {
        // 假设已经创建了具体的支付服务实例
        AlipayService alipayService = new AlipayService();
        WechatPayService wechatPayService = new WechatPayService();
        UnionPayService unionPayService = new UnionPayService();

        // 创建适配器实例
        PaymentAdapter alipayAdapter = new AlipayAdapter(alipayService);
        PaymentAdapter wechatPayAdapter = new WechatPayAdapter(wechatPayService);
        PaymentAdapter unionPayAdapter = new UnionPayAdapter(unionPayService);

        // 统一调用支付方法
        alipayAdapter.pay("12345", 100.0);
        wechatPayAdapter.pay("67890", 200.0);
        unionPayAdapter.pay("11111", 300.0);
    }
}

三、适配器模式在支付接口适配中的优势

  • 统一接口:通过定义一个通用的支付接口,将不同的支付方式统一起来,客户端代码只需要关心这个通用接口,而不需要关心具体的支付实现细节。
  • 提高可维护性:当需要添加新的支付方式时,只需要创建一个新的适配器类,而不需要修改客户端代码。这符合开闭原则,提高了系统的可维护性。
  • 降低耦合度:客户端代码与具体的支付实现解耦,客户端只需要依赖通用的支付接口,而不需要直接依赖具体的支付服务类,降低了系统的耦合度。

适配器模式在处理多种支付接口的适配问题时,能够有效地解决接口不兼容的问题,提高代码的可维护性和可扩展性,是一种非常实用的设计模式。

相关推荐
兩尛23 分钟前
maven高级(day15)
java·开发语言·maven
xiao--xin1 小时前
LeetCode100之搜索二维矩阵(46)--Java
java·算法·leetcode·二分查找
end_SJ1 小时前
c语言 --- 字符串
java·c语言·算法
zzyh1234561 小时前
spring cloud 负载均衡策略
java·spring cloud·负载均衡
涔溪1 小时前
JS二叉树是什么?二叉树的特性
java·javascript·数据结构
zzyh1234561 小时前
springcloud负载均衡原理
java·spring cloud·负载均衡
东北赵四2 小时前
JVM之垃圾回收器G1概述的详细解析
java·开发语言·jvm
2403_875180952 小时前
一键掌握多平台短视频矩阵营销/源码部署
java·前端·数据结构·线性代数·矩阵·php
向阳12182 小时前
doris:手动分区
java·服务器·windows·doris
uzong2 小时前
新公司在使用的 Hibernate Validator 框架
java·后端