适配器设计模式

2023-11-03

目录

前言:

 适配器原理与实现

 适配器模式的应用场景

1.封装有缺陷的接口

 2.统一多个类的接口设计

3.替换依赖的外部系统

4.兼容老版本接口

 5.适配不同格式的数据

代理、桥接、装饰器、适配器 4 种设计模式的区别

参考资料 


前言:

     适配器模式这个模式相对来说还是比较简单、好理解的,应用场景也很具体,总体上来讲比较好掌握。顾名思义其主要是为了做适配、为了兼容不同的接口。适配器模式有两种不同的实现模式,类适配器和对象适配器。有5种比较常见的应用场景。


 适配器原理与实现

         适配器模式顾名思义,这个模式就是用来做适配的,它将不兼容的接口转换为可兼容的接口,让原本由于接口不兼容而不能一起工作的类可以一起工作。对于这个模式,有一个经常被拿来解释它的例子,就是 USB 转接头充当适配器,把两种不兼容的接口,通过转接变得可以一起工作。

     原理很简单,我们再来看下它的代码实现。适配器模式有两种实现方式:类适配器和对象适配器。其中,类适配器使用继承关系来实现,对象适配器使用组合关系来实现。具体的代码实现如下所示。其中,ITarget 表示要转化成的接口定义。Adaptee(被适配者) 是一组不兼容 ITarget 接口定义的接口,Adaptor(适配者) Adaptee 转化成一组符合 ITarget 接口定义的接口


// 类适配器: 基于继承
public interface ITarget {
  void f1();
  void f2();
  void fc();
}

public class Adaptee {
  public void fa() { //... }
  public void fb() { //... }
  public void fc() { //... }
}

public class Adaptor extends Adaptee implements ITarget {
  public void f1() {
    super.fa();
  }
  
  public void f2() {
    //...重新实现f2()...
  }
  
  // 这里fc()不需要实现,直接继承自Adaptee,这是跟对象适配器最大的不同点
}

// 对象适配器:基于组合
public interface ITarget {
  void f1();
  void f2();
  void fc();
}

public class Adaptee {
  public void fa() { //... }
  public void fb() { //... }
  public void fc() { //... }
}

public class Adaptor implements ITarget {
  private Adaptee adaptee;
  
  public Adaptor(Adaptee adaptee) {
    this.adaptee = adaptee;
  }
  
  public void f1() {
    adaptee.fa(); //委托给Adaptee
  }
  
  public void f2() {
    //...重新实现f2()...
  }
  
  public void fc() {
    adaptee.fc();
  }
}

  针对这两种实现方式,在实际的开发中,到底该如何选择使用哪一种呢?判断的标准主要有两个,一个是 Adaptee 接口的个数,另一个是 Adaptee ITarget 的契合程度。

  • 如果 Adaptee 接口并不多,那两种实现方式都可以。
  • 如果 Adaptee 接口很多,而且 Adaptee ITarget 接口定义大部分都相同,那我们推荐使用类适配器,因为 Adaptor 复用父类 Adaptee 的接口,比起对象适配器的实现方式,Adaptor 的代码量要少一些。
  • 如果 Adaptee 接口很多,而且 Adaptee ITarget 接口定义大部分都不相同,那我们推荐使用对象适配器,因为组合结构相对于继承更加灵活。

 适配器模式的应用场景

      一般来说,适配器模式可以看作一种补偿模式,用来补救设计上的缺陷。应用这种模式算是无奈之举。如果在设计初期,我们就能协调规避接口不兼容的问题,那这种模式就没有应用的机会了。

1.封装有缺陷的接口

       假设我们依赖的外部系统在接口设计方面有缺陷(比如包含大量静态方法),引入之后会影响到我们自身代码的可测试性。为了隔离设计上的缺陷,我们希望对外部系统提供的接口进行二次封装,抽象出更好的接口设计,这个时候就可以使用适配器模式了。


public class CD { //这个类来自外部sdk,我们无权修改它的代码
  //...
  public static void staticFunction1() { //... }
  
  public void uglyNamingFunction2() { //... }

  public void tooManyParamsFunction3(int paramA, int paramB, ...) { //... }
  
   public void lowPerformanceFunction4() { //... }
}

// 使用适配器模式进行重构
public interface ITarget {
  void function1();
  void function2();
  void fucntion3(ParamsWrapperDefinition paramsWrapper);
  void function4();
  //...
}
// 注意:适配器类的命名不一定非得末尾带Adaptor
public class CDAdaptor extends CD implements ITarget {
  //...
  public void function1() {
     super.staticFunction1();
  }
  
  public void function2() {
    super.uglyNamingFucntion2();
  }
  
  public void function3(ParamsWrapperDefinition paramsWrapper) {
     super.tooManyParamsFunction3(paramsWrapper.getParamA(), ...);
  }
  
  public void function4() {
    //...reimplement it...
  }
}

 2.统一多个类的接口设计

      某个功能的实现依赖多个外部系统(或者说类)。通过适配器模式,将它们的接口适配为统一的接口定义,然后我们就可以使用多态的特性来复用代码逻辑。具体我还是举个例子来解释一下。

       假设我们的系统要对用户输入的文本内容做敏感词过滤,为了提高过滤的召回率,我们引入了多款第三方敏感词过滤系统,依次对用户输入的内容进行过滤,过滤掉尽可能多的敏感词。但是,每个系统提供的过滤接口都是不同的。这就意味着我们没法复用一套逻辑来调用各个系统。这个时候,我们就可以使用适配器模式,将所有系统的接口适配为统一的接口定义,这样我们可以复用调用敏感词过滤的代码。3


public class ASensitiveWordsFilter { // A敏感词过滤系统提供的接口
  //text是原始文本,函数输出用***替换敏感词之后的文本
  public String filterSexyWords(String text) {
    // ...
  }
  
  public String filterPoliticalWords(String text) {
    // ...
  } 
}

public class BSensitiveWordsFilter  { // B敏感词过滤系统提供的接口
  public String filter(String text) {
    //...
  }
}

public class CSensitiveWordsFilter { // C敏感词过滤系统提供的接口
  public String filter(String text, String mask) {
    //...
  }
}

// 未使用适配器模式之前的代码:代码的可测试性、扩展性不好
public class RiskManagement {
  private ASensitiveWordsFilter aFilter = new ASensitiveWordsFilter();
  private BSensitiveWordsFilter bFilter = new BSensitiveWordsFilter();
  private CSensitiveWordsFilter cFilter = new CSensitiveWordsFilter();
  
  public String filterSensitiveWords(String text) {
    String maskedText = aFilter.filterSexyWords(text);
    maskedText = aFilter.filterPoliticalWords(maskedText);
    maskedText = bFilter.filter(maskedText);
    maskedText = cFilter.filter(maskedText, "***");
    return maskedText;
  }
}

// 使用适配器模式进行改造
public interface ISensitiveWordsFilter { // 统一接口定义
  String filter(String text);
}

public class ASensitiveWordsFilterAdaptor implements ISensitiveWordsFilter {
  private ASensitiveWordsFilter aFilter;
  public String filter(String text) {
    String maskedText = aFilter.filterSexyWords(text);
    maskedText = aFilter.filterPoliticalWords(maskedText);
    return maskedText;
  }
}
//...省略BSensitiveWordsFilterAdaptor、CSensitiveWordsFilterAdaptor...

// 扩展性更好,更加符合开闭原则,如果添加一个新的敏感词过滤系统,
// 这个类完全不需要改动;而且基于接口而非实现编程,代码的可测试性更好。
public class RiskManagement { 
  private List<ISensitiveWordsFilter> filters = new ArrayList<>();
 
  public void addSensitiveWordsFilter(ISensitiveWordsFilter filter) {
    filters.add(filter);
  }
  
  public String filterSensitiveWords(String text) {
    String maskedText = text;
    for (ISensitiveWordsFilter filter : filters) {
      maskedText = filter.filter(maskedText);
    }
    return maskedText;
  }
}

3.替换依赖的外部系统

 当我们把项目中依赖的一个外部系统替换为另一个外部系统的时候,利用适配器模式,可以减少对代码的改动。具体的代码示例如下所示:


// 外部系统A
public interface IA {
  //...
  void fa();
}
public class A implements IA {
  //...
  public void fa() { //... }
}
// 在我们的项目中,外部系统A的使用示例
public class Demo {
  private IA a;
  public Demo(IA a) {
    this.a = a;
  }
  //...
}
Demo d = new Demo(new A());

// 将外部系统A替换成外部系统B
public class BAdaptor implemnts IA {
  private B b;
  public BAdaptor(B b) {
    this.b= b;
  }
  public void fa() {
    //...
    b.fb();
  }
}
// 借助BAdaptor,Demo的代码中,调用IA接口的地方都无需改动,
// 只需要将BAdaptor如下注入到Demo即可。
Demo d = new Demo(new BAdaptor(new B()));

4.兼容老版本接口

 在做版本升级的时候,对于一些要废弃的接口,我们不直接将其删除,而是暂时保留,并且标注为 deprecated,并将内部实现逻辑委托为新的接口实现。这样做的好处是,让使用它的项目有个过渡期,而不是强制进行代码修改。这也可以粗略地看作适配器模式的一个应用场景。


public class Collections {
  public static Emueration emumeration(final Collection c) {
    return new Enumeration() {
      Iterator i = c.iterator();
      
      public boolean hasMoreElments() {
        return i.hashNext();
      }
      
      public Object nextElement() {
        return i.next():
      }
    }
  }
}

 5.适配不同格式的数据

 前面我们讲到,适配器模式主要用于接口的适配,实际上,它还可以用在不同格式的数据之间的适配。比如,把从不同征信系统拉取的不同格式的征信数据,统一为相同的格式,以方便存储和使用。再比如,Java 中的 Arrays.asList() 也可以看作一种数据适配器,将数组类型的数据转化为集合容器类型。


List<String> stooges = Arrays.asList("Larry", "Moe", "Curly");

代理、桥接、装饰器、适配器 4 种设计模式的区别

      代理、桥接、装饰器、适配器,这 4 种模式是比较常用的结构型设计模式。它们的代码结构非常相似。笼统来说,它们都可以称为 Wrapper 模式,也就是通过 Wrapper 类二次封装原始类。

     尽管代码结构相似,但这 4 种设计模式的用意完全不同,也就是说要解决的问题、应用场景不同,这也是它们的主要区别。

这里我就简单说一下它们之间的区别。

代理模式:代理模式在不改变原始类接口的条件下,为原始类定义一个代理类,主要目的是控制访问,而非加强功能,这是它跟装饰器模式最大的不同。

桥接模式:桥接模式的目的是将接口部分和实现部分分离,从而让它们可以较为容易、也相对独立地加以改变。

装饰器模式:装饰者模式在不改变原始类接口的情况下,对原始类功能进行增强,并且支持多个装饰器的嵌套使用。

适配器模式:适配器模式是一种事后的补救策略。适配器提供跟原始类不同的接口,而代理模式、装饰器模式提供的都是跟原始类相同的接口。

参考资料 

51 | 适配器模式:代理、适配器、桥接、装饰,这四个模式有何区别?-极客时间

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

适配器设计模式 的相关文章

  • Chain of Responsibility:责任链模式

    现有多个处理者 这些处理者可以处理不同的请求 这些处理者有等级关系 每个处理者都有更上级的处理者 对于最上级的处理者 可以没有上级 对于一个上级处理者 可以有多个下级处理者 客户端发出了一个请求 请求被传递给一个最下级处理者 而该处理者无法
  • C++设计模式(8)——命令模式

    命令模式 亦称 动作 事务 Action Transaction Command 意图 命令模式是一种行为设计模式 它可将请求转换为一个包含与请求相关的所有信息的独立对象 该转换让你能根据不同的请求将方法参数化 延迟请求执行或将其放入队列中
  • 设计模式-单一职责原则介绍与理解

    描述 一个类应该专注于实现一个功能 好处 便于代码复用 举例 俄罗斯方块游戏 首先可以想到的是游戏逻辑与界面的分离 也就是说逻辑一个类 界面部分一个类 这样做的好处就是我们可以复用游戏逻辑的代码 例如我们用java写了一个基于PC端的俄罗斯
  • 行为型模式-策略模式

    package per mjn pattern strategy 抽象策略类 public interface Strategy void show package per mjn pattern strategy 具体策略类 用来封装算法
  • 设计模式之享元模式

    一 背景 在面向对象程序设计过程中 有时会面临要创建大量相同或相似对象实例的问题 创建那么多的对象将会耗费很多的系统资源 它是系统性能提高的一个瓶颈 例如 围棋和五子棋中的黑白棋子 图像中的坐标点或颜色 局域网中的路由器 交换机和集线器 教
  • 简单工厂模式

    简单工厂模式 一 概念 从设计模式的类型上来说 简单工厂模式是属于创建型模式 又叫做静态工厂方法 StaticFactory Method 模式 但不属于23种GOF设计模式之一 简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例 简
  • Java设计模式-装饰者模式Decorator

    介绍 装饰者模式的核心思想是通过创建一个装饰对象 即装饰者 动态扩展目标对象的功能 并且不会改变目标对象的结构 提供了一种比继承更灵活的替代方案 需要注意的是 装饰对象要与目标对象实现相同的接口 或继承相同的抽象类 另外装饰对象需要持有目标
  • C++设计模式-State状态模式

    State状态模式作用 当一个对象的内在状态改变时允许改变其行为 这个对象看起来像是改变了其类 UML图如下 State类 抽象状态类 定义一个接口以封装与Context的一个特定状态相关的行为 ConcreteState类 具体状态 每一
  • 设计模式——导论

    作为软件开发人员 我们在平时工作的过程中 往往需要编写很多的代码来实现我们的需求 很多的时候会造成代码臃肿和代码冗余的情况 这个时候我们需要引入一个理念 设计模式 设计模式存在的意义在于 1 使得我们的代码更加精炼 2 使我们代码的可读性更
  • 设计模式学习之装饰器模式

    装饰器 Decorator 模式跟适配器 Adapter 模式一样 属于构建型设计模式 在学习适配器模式的时候说过 适配器模式的重点在复用能力 装饰器模式的重点在扩展能力 换言之 装饰器模式是先复用后扩展 这也导致了很多人跟适配器模式混淆
  • 设计模式一之简单工厂模式

    生活示例 刻板印刷 gt 活字印刷举例 喝酒唱歌 人生真爽 gt 对酒当歌 人生几何 可维护 需要修改 则只需要改需要改的字即可 可复用 字体可在后来的印刷复用 可扩展 需要加字 只需要加入相应的刻字即可 灵活性好 字的排列可能竖排或横排
  • 设计模式--Abstract server模式 VS Adapter 模式

    适配器类似于现实世界里面的插头 通过适配器 我们可以将分属于不同类的两种不同类型的数据整合起来 而不必去根据某一需要增加或者修改类里面的方法 Adapter mode和Proxymode的区别 Proxy的关注点是职能转移 通过引入Prox
  • 设计模式(5)-适配器模式(Adapter Pattern)

    适配器模式 Adapter Pattern 顾名思义 就像变压器 转接头差不多 就像美国的生活电压是110V 中国是220V 就需要一个变压器将220V转换成110V 或者一个Type C接口想插如USB接口的东西 你就需要一个转换器 而这
  • 分享几个项目中用到的设计模式

    前言 之前项目中出于扩展性和有雅性的考虑 使用了多种设计模式进行项目框架的设计 主要的一些设计模式是单例模式 工厂模式 策略模式 责任链模式 代理模式这几种 现在依次讲讲这几个的主要是实现方式和在我们项目中的应用场景 核心设计模式分享 单例
  • 第12课:生活中的构建模式——想要车还是庄园

    用程序来模拟生活 从剧情中思考构建模式 与工厂模式的区别 与组合模式的区别 构建模式的模型抽象 类图 基于升级版的实现 模型说明 应用场景 故事剧情 下周就要过年了 这是 Tony 工作后的第一个春节 还是在离家这么远的北京工作 所以肯定不
  • 设计模式之享元模式

    享元模式 就是共享技术 对于系统中存在大量相同的对象 把他们抽取成一个对象放在缓存中进行使用 这样可以大大节省系统资源 例如 围棋棋盘上有两种棋子 一个是黑子 一个是白子 如果在下棋的时候每下一个棋子就要new一个棋子对象 那么就会有大量的
  • Java监听器与观察者模式

    Java监听器与观察者模式 Java中的监听器 Listener 和观察者模式 Observer Pattern 都是用于处理对象间的事件通知和响应的设计模式 它们的目的是在对象之间建立一种松散的耦合 使得一个对象的状态变化可以通知到其他对
  • 设计模式—迭代器模式解析

    本文参考学习了 图解设计模式 中的代码实现和原理解释 迭代器模式 简介 Iterator 模式用于在数据集合中按照顺序遍历集合 就类似于我们的循环 一个个去遍历一个集合中的所有元素 示例代码 首先我们思考一个书本和书架的关系 显然 书架可以
  • Java设计模式:模板方法模式

    作者主页 欢迎来到我的技术博客 个人介绍 大家好 本人热衷于 Java后端开发 欢迎来交流学习哦 如果文章对您有帮助 记得 关注 点赞 收藏 评论 您的支持将是我创作的动力 让我们一起加油进步吧 文章目录 一 模板方法模式的定义 二 模板方
  • 【设计模式之美】面向对象分析方法论与实现(二):需求到接口实现的方法论

    文章目录 一 进行面向对象设计 1 划分职责 gt 需要有哪些类 2 定义类及其属性和方法 3 定义类与类之间的交互关系 4 将类组装起来并提供执行入口 二 如何进行面向对象编程 1 接口实现

随机推荐

  • 如何搭建一个完整的Vue3.0 + ts 的项目

    如何搭建一个完整的Vue3 0 ts 的项目 相信9月18日尤大大的关于Vue3 0的发表演讲大家一定有所关注 现在Vue3 0 也已经进入RC阶段 最终产品的候选版本 如果没有问题则可发布成为正式版本 所以Vue3 0的学习是我们必然的趋
  • matlab无人机路径规划,一种新的优化算法,天牛群算法,比蚁群算法效果好,收敛快

    matlab无人机路径规划 一种新的优化算法 天牛群算法 比蚁群算法效果好 收敛快 在tsp qap二次规划以及三维路径规划上有很好的效果 ID 51100647321750445tbNick id4mr
  • CSDN周赛58期后记,及往期题目记录

    本期周赛没什么bug 体验比较丝滑 提出表扬 咱也不能老吐槽不是 当然 丝滑的根本原因是题目比较水 尤其是最后一道 莫名其妙的键盘 两周前的 56 期刚刚考过 当真是莫名其妙而又迫不及待地又考了一遍 是怕大家背得不够熟吗 而从难度上看 这道
  • go语言安装环境

    go语言安装环境 1 安装包下载 2 安装 最好不要有中文路径 3 测试环境 cmd中输入 go version 回车 4 经典代码 保存文件名 hello go package main import fmt 包含println func
  • c++拆分string型字符串存到vector中

    对数据进行拆分放到vec中 void split string const std string str std vector
  • 关于IDEA无法启动常见的几个问题

    IDEA无法启动 常见的问题及解决办法 第一种 无法启动 大多是因为javaagent 那一行 这种比较简单 出现这种问题 大都是因为编辑vmoptions时出错造成的 这种只需要删除idea 的配置文件就行 一般默认在C盘 例如 C Us
  • Python requests“Max retries exceeded with url” error

    今天写python网络爬虫的时候遇到一个问题 报错的具体内容如下 HTTPConnectionPool host dds cr usgs gov port 80 Max retries exceeded with url ltaauth s
  • 机器学习——朴素贝叶斯、后验概率最大和极大似然

    朴素贝叶斯没有参数估计 给堆数据直接求 属于生成模型 不用优化模型求最佳参数 这区别于判别模型 我遇到的困惑 1 后验概率最大 和 极大似然 这二者有什么区别和联系吗 2 朴素贝叶斯和EM这二者有什么区别和联系吗 答案 1 朴素贝叶斯是根据
  • 怎么维护自己的电脑?

    方向一 我的电脑介绍 我使用的是一台来自知名品牌的笔记本电脑 它具有高性能的核心配置 如快速处理器 大容量内存和高性能显卡 以及宽敞的存储空间 我选择这台电脑主要是因为它的出色性能和可靠性 能够满足我在学习和工作中的需求 方向二 我的日常维
  • 数据分析Power BI数据建模教程(四)——如何创建计算度量值和计算表

    Power BI 是基于云的商业数据分析和共享工具 它能帮您把复杂的数据转化成最简洁的视图 通过它 您可以快速创建丰富的可视化交互式报告 即使在外也能用手机端 APP 随时查看 甚至检测公司各项业务的运行状况 只需它仪表板的一个界面就够了
  • 使用yum安装nginx,几步完成,超级简单!(亲测)

    1 安装yum utils工具包 sudo yum install yum utils 2 设置yum存储库 进入 etc yum repos d目录 cd etc yum repos d 创建nginx repo文件 vim nginx
  • AMIS + httplib 快速搭建前后端

    AMIS httplib 快速搭建前后端 1 简介 1 1 AMIS简介 1 2 httplib开源库 2 快速使用 2 1 AMIS环境搭建 2 2 创建一个登陆页面 2 3 利用httplib搭建简单的后端 2 4 运行 尾声总结 1
  • 基于python手动画出spectrogram(语谱图)

    Spectrogram是基于STFT变换得到的 非常有助于分析信号的时频特性 在语音信号处理中常被称为 语谱图 python中有一些写好的模块可以直接将时域的信号转化成spectrogram 但这并不利于对其原理的理解 而且横纵左边的转换也
  • Vue中token刷新及续期的功能实现

    在vue中如何实现token续期 刷新token 原因 最近公司项目有一个视频播放的功能 然后由于在测试环境登录时token过期时间较短导致一直在当前页面播放视频会出现token过期的现象 然后用户刷新会触发axios响应拦截器的操作退出系
  • Day01.二分查找、移除元素

    Day01 二分查找 移除元素 0704 二分查找 题目链接 0704 二分查找 思路 二分查找 仅对有序数组有效 每次需要数组的中间值 与目标值比较大小 如果中间值比目标值大 说明目标值位置在left与mid中间 区间缩小一半 同理 如果
  • 蓝桥杯试题联系

    题目 你有一架天平 现在你要设计一套砝码 使得利用这些砝码可以称出任意 小于等于 N 的正整数重量 那么这套砝码最少需要包含多少个砝码 注意砝码可以放在天平两边 输入格式 输入包含一个正整数 N 输出格式 输出一个整数代表答案 样例输入 7
  • 2D离散傅里叶变换及逆变换的matlab实现

    积分线性变换可以将信号或图像在更适合的域内表达 并且使得解决相关问题更容易 在图像分析中最常用的积分显示变换是傅里叶变换 离散余弦变换与小波变换 1d傅里叶变换由傅里叶 Fourier 提出 1d傅里叶变换将函数f x 变换到频率域F t
  • android 屏幕适配框架,Android屏幕适配

    为什么要进行Android屏幕适配 由于Android系统的开放性 任何用户 开发者 OEM厂商 运营商都可以对Android进行定制 于是导致 1 Android系统碎片化 小米定制的MIUI 魅族定制的flyme 华为定制的EMUI等等
  • 组合式 API - 生命周期钩子

    选项式 API Hook inside setup beforeCreate Not needed gt setup created Not needed gt setup beforeMount onBeforeMount mounted
  • 适配器设计模式

    目录 前言 适配器原理与实现 适配器模式的应用场景 1 封装有缺陷的接口 2 统一多个类的接口设计 3 替换依赖的外部系统 4 兼容老版本接口 5 适配不同格式的数据 代理 桥接 装饰器 适配器 4 种设计模式的区别 参考资料 前言 适配器