设计模式之适配器模式:接口对接丝般顺滑(图代码解析面面俱到)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 设计模式之适配器模式:接口对接丝般顺滑(图代码解析面面俱到)

概要

概念

    适配器模式是一种结构型设计模式,用于将一个类的接口转换成客户端所期望的另一个接口。适配器模式通过创建一个适配器类,将原始接口转换为目标接口,使得两个不兼容的类可以协同工作。

组成

适配器模式由以下几个主要组件构成:

  • 目标接口(Target ):客户端期望的接口,适配器将原始接口转换为目标接口。
  • 原始接口(Adaptee ):需要被适配的类的接口。
  • 适配器(Adapter):实现目标接口,同时持有一个原始接口的引用,在目标接口方法中调用原始接口方法来完成适配。

类图

工作原理

  1. 适配器模式:将一个类的接口转换成另一种接口.让原本接口不兼容的类可以兼容
  2. 从用户的角度看不到被适配者,是解耦的
  3. 用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
  4. 用户收到反馈结果,感觉只是和目标接口交互,如图(关键词:src,adapter,dst )

应用场景

    当需要使用一个已存在的类,但其接口与其他类不兼容时,可以使用适配器模式进行接口转换。

    当想要创建一个可复用的类,该类能与多个不相关的类或类层次结构协同工作时,可以使用适配器模式。

优点

1、可以让已存在的类与其他类协同工作,无需重写已有代码。

2、可以将实现细节封装在适配器中,对客户端隐藏具体的实现细节。

3、可以提高代码的可复用性和灵活性,适配器可以用于多种不同的上下文。

类型

类适配器模式

    在类适配器模式中,适配器实现目标接口并继承原始类,通过重写目标方法,调用原始类的方法来完成适配。这种方式需要多重继承支持,方便但局限性较大。

对象适配器模式

    在对象适配器模式中,适配器持有一个原始类的引用,并实现目标接口,目标方法调用原始类的方法来完成适配。这种方式不需要多重继承支持,更加灵活。

两者区别

    类适配器和对象适配器的区别在于它们如何与被适配类(Adaptee)进行连接。

    类适配器使用多重继承,同时继承目标接口(Target)和被适配类(Adaptee)。通过继承的方式,适配器类可以通过调用被适配类的方法来实现目标接口的方法。

    对象适配器使用关联(组合),在适配器类内部持有一个被适配类的实例。适配器类通过调用被适配类实例的方法来实现目标接口的方法。

    总结起来是:类适配器会继承被适配类的接口以及实现,然后将其转换为目标接口;而对象适配器则是利用组合的方式将被适配类的实例嵌入到适配器中,进而实现目标接口

二维表展示:

类适配器 对象适配器
实现方式 使用类的继承关系实现适配 使用对象的组合关系实现适配
关联关系 适配器类同时继承目标接口和被适配者类 适配器持有被适配者对象的引用
可扩展性 不支持适配多个被适配者类 可以适配多个被适配者对象
适配方式 通过继承实现适配,可以重写被适配者的方法 通过组合实现适配,可以调用被适配者的方法
依赖关系 类适配器依赖于被适配者类 对象适配器依赖于被适配者对象

    注意:在C++等支持多重继承的语言中,适配器类可以同时继承目标接口和被适配类,从而实现对两者的继承。而在Java等不支持多重继承的语言中,可以通过实现目标接口和关联被适配类的方式来实现相同的逻辑。

    但是,由于多重继承可能会带来一些问题,如命名冲突、菱形继承等,因此在使用类适配器模式时需要注意继承关系的设计和维护,避免潜在的问题。

    还有一种说法是适配器分成三类,多了一个接口适配器,这三种方式,是根据adeptee在Adapter里的形式来分类,也是命名的由来。

  • 类适配器:在Adapter里,将adeptee当做类,来继承;
  • 对象适配器:在Adapter里,将adeptee作为一个对象,来持有
  • 接口适配器:在Adapter里,将adeptee作为一个接口,来实现

示例代码

    类适配器模式示例(java 单继承,用接口和类模仿继承自两个类的效果)

// 目标接口
interface Target {
    void request();
}
// 原始类
class Adaptee {
    void specificRequest() {
        System.out.println("执行特殊请求");
    }
}
// 类适配器
class ClassAdapter extends Adaptee implements Target {
    public void request() {
        specificRequest();
    }
}
// 客户端代码
public class Client {
    public static void main(String[] args) {
        Target target = new ClassAdapter();
        target.request();
    }
}

     对象适配器模式示例

// 目标接口
interface Target {
    void request();
}
// 原始类
class Adaptee {
    void specificRequest() {
        System.out.println("执行特殊请求");
    }
}
// 对象适配器
class ObjectAdapter implements Target {
    private Adaptee adaptee;//持有一个被适配类的实例
    ObjectAdapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }
    public void request() {
        adaptee.specificRequest();
    }
}
// 客户端代码
public class Client {
    public static void main(String[] args) {
        Adaptee adaptee = new Adaptee();
        Target target = new ObjectAdapter(adaptee);
        target.request();
    }
}

实现(对象适配器详解)

    以姚明在NBA打球为例讲解一下适配器模式

业务背景

    在姚明(外籍中锋)加入NBA之处,英文不熟悉,这时候就需要一位翻译在他和教练之间进行沟通。

代码

//抽象球员,包含attack和defense的方法
abstract class Player {
    protected String name;
    public Player(String name){
        this.name=name;
    }
    public Player(){};//空构造为的是表现翻译者的隐身
    public abstract void attack();//进攻
    public abstract void defense();//防守
}
//外籍中锋
public class ForeignCenter {
    private String name;
   public String getName(){
       return this.name;
   }
   public void setName(String name){
       this.name=name;
   }
    public void 进攻(){
        System.out.println("外籍中锋"+name+"进攻");
    }
    public void 防守(){
        System.out.println("外籍中锋"+name+"防守");
    }
}
//翻译(适配器)
public class Translator extends Player {
    private ForeignCenter foreignCenter = new ForeignCenter();
    public Translator(String name) {
        //super(name);
        foreignCenter.setName(name);//给姚明配的翻译,翻译一出生就转配给了姚明
    }
    @Override
    public void attack() {
        foreignCenter.进攻();
    }
    @Override
    public void defense() {
        foreignCenter.防守();
    }
}
//客户端
public class Client {
    public static void main(String[] args) {
        Player center=new Translator("姚明");//翻译隐身
        System.out.println(center.name);//这里会显示null
        center.attack();
        center.defense();//姚明其实听不懂这两句,需要翻译
    }
}

    这里有两个不容易觉察又很重要的地方:

    1、在翻译者类当中,attach和defense两个方法,实际分别调用的都是外籍中锋的进攻和防守方法,这里是用关联的方式实现的逻辑继承,关于这点在后面会细说。

    2、这里做了个小埋伏,让我们看到翻译者的“隐身”

    客户端给翻译者传的名字是姚明,结合业务就是教练叫姚明进攻,姚明听不懂,但是翻译听懂了,翻译再翻译给姚明,“姚明”通过翻译者的构造函数传给谁了呢?看下面的代码

    可以看到这个名字是赋给了外籍中锋,这里我特意在球员类里面添加了一个空的构造函数

    现在来看,客户端中的Translator表面看是Player类型,通过构造函数传进去的名字,也给了ForeignCenter,所以,Center.name就是null,这也是适配器的魅力所在,在无形中解决了接口不相容的问题。

常见问题

为什么有适配器模式

    适配器模式的主要目的是解决两个不兼容的接口之间的兼容性问题。在软件开发过程中,经常会遇到以下情况导致接口不兼容:

    系统需要使用已存在的类,但其接口与系统要求的接口不一致:当我们需要使用某个类的功能,但其接口与我们现有的系统接口不同,无法直接对接,这时候适配器模式可以通过创建适配器来将已存在的类的接口转换为系统要求的接口,从而使这个类能够被系统使用。

    需要复用一些功能类,但这些类的接口与系统接口不兼容:在系统设计过程中可能会存在一些功能优秀的类,我们希望能够将它们复用于系统中,但是由于这些类的接口与系统接口不一致,无法直接复用,这时候适配器模式可以通过创建适配器来将这些类的接口转换为系统接口,从而让它们能够被复用。

    适配器模式的出现可以降低代码的耦合性,使得不兼容的接口能够协调工作。它能够提高代码的可复用性和灵活性,并且将适配过程封装在适配器中,对客户端隐藏了具体的实现细节,符合面向对象设计原则中的封装和抽象原则。

适配器模式告诉我们什么

    在软件设计中尽量避免使用适配器模式,而是在接口设计阶段就考虑兼容性问题并设计好接口。会的目的是不出现这样的问题

    在软件设计中,应该尽可能提前考虑这些因素,并遵循良好的接口设计原则,以避免后期出现兼容性问题。这包括:

  • 接口设计要明确、简洁、易于理解和使用,遵循单一职责原则和接口隔离原则。
  • 合理规划系统的接口,预见可能的变化和需求,避免频繁修改接口。
  • 使用设计模式和设计原则来约束和指导接口设计,例如依赖倒置原则、开闭原则等。

    总之,尽管适配器模式在某些情况下是非常有用的,但在软件设计过程中,应该尽量避免使用适配器模式,而是通过良好的接口设计来解决兼容性问题。这样可以提高代码的可读性、可维护性和可扩展性,减少后期修改和重构的工作量。

适配器模式体现了哪些设计原则

    单一职责原则(Single Responsibility Principle):每个类都只有一个职责,Adaptee 是适配器模式中的已有类,Adapter 是适配器类,Target 是目标接口,它们各自承担不同的职责。适配器类的职责就是进行接口转换,只有一个职责。

    开闭原则(Open-Closed Principle):适配器模式通过引入适配器类,可以在不修改原有代码的情况下,实现与新接口的兼容,符合开闭原则的要求。

    接口隔离原则(Interface Segregation Principle):抽象目标接口 Target 只包含客户端所需的方法,避免了客户端直接依赖于不需要的方法。

关联方式实现了逻辑继承

    Adapter 类通过关联一个 Adaptee 对象来实现适配器功能。当 Adapter 的 request() 方法被调用时,它实际上会调用被适配类 Adaptee 的 specificRequest() 方法。(请配合上面的类图看)。适配器类(Adapter)包含一个被适配的类(Adaptee)的实例,并在目标接口的方法中调用被适配类的相应方法来实现适配。作为一个中间层,适配器类(Adapter)不仅提供了目标接口的实现,还继承了被适配类(Adaptee)的功能,实现了逻辑上的继承关系。当客户端调用适配器类的方法时,实际上是通过适配器类来调用被适配类的方法,这就体现了适配器类对被适配类的逻辑继承。

适配器模式在SpringMVC框架应用

    SpringMvc 中的 HandlerAdapter , 就使用了适配器模式

    使用 HandlerAdapter 的原因:处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用Controller方法,需要调用的时候就得不断是使用if else来进行判断是哪一种子类然后执行。那么如果后面要扩展Controller,就得修改原来的代码,这样违背了开闭原则。

    Spring 定义了一个适配接口,使得每一种 Controller 有一种对应的适配器实现类,适配器代替controller执行相应的方法,扩展 Controller 时,只需要增加一个适配器类就完成了SpringMVC的扩展了。

总结

    适配器模式是一种非常实用的设计模式,通过将不兼容的接口转换为目标接口,使得原本无法协同工作的类能够协作。它能够提高代码的复用性和灵活性,并且将实现细节封装在适配器中,对客户端隐藏具体的实现细节。但是,重点是学习适配器模式的目的是让我们尽量避免使用它,在软件设计中,应该尽可能提前考虑这些因素,并遵循良好的接口设计原则,以避免后期出现兼容性问题。

相关文章
|
1月前
|
存储 安全 Java
系统安全架构的深度解析与实践:Java代码实现
【11月更文挑战第1天】系统安全架构是保护信息系统免受各种威胁和攻击的关键。作为系统架构师,设计一套完善的系统安全架构不仅需要对各种安全威胁有深入理解,还需要熟练掌握各种安全技术和工具。
93 10
|
1月前
|
前端开发 JavaScript 开发者
揭秘前端高手的秘密武器:深度解析递归组件与动态组件的奥妙,让你代码效率翻倍!
【10月更文挑战第23天】在Web开发中,组件化已成为主流。本文深入探讨了递归组件与动态组件的概念、应用及实现方式。递归组件通过在组件内部调用自身,适用于处理层级结构数据,如菜单和树形控件。动态组件则根据数据变化动态切换组件显示,适用于不同业务逻辑下的组件展示。通过示例,展示了这两种组件的实现方法及其在实际开发中的应用价值。
35 1
|
2月前
|
设计模式 PHP 开发者
PHP中的设计模式:桥接模式的解析与应用
在软件开发的浩瀚海洋中,设计模式如同灯塔一般,为开发者们指引方向。本文将深入探讨PHP中的一种重要设计模式——桥接模式。桥接模式巧妙地将抽象与实现分离,通过封装一个抽象的接口,使得实现和抽象可以独立变化。本文将阐述桥接模式的定义、结构、优缺点及其应用场景,并通过具体的PHP示例代码展示如何在实际项目中灵活运用这一设计模式。让我们一起走进桥接模式的世界,感受它的魅力所在。
|
2月前
|
设计模式 算法 数据库连接
PHP中的设计模式:提高代码的可维护性和扩展性
【10月更文挑战第13天】 本文将探讨PHP中常见的设计模式及其在实际项目中的应用。通过对比传统编程方式,我们将展示设计模式如何有效地提高代码的可维护性和扩展性。无论是单例模式确保类的单一实例,还是观察者模式实现对象间的松耦合,每一种设计模式都为开发者提供了解决特定问题的最佳实践。阅读本文后,读者将能更好地理解和应用这些设计模式,从而提升PHP编程的效率和质量。
|
2月前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入解析与实践
在PHP开发中,设计模式是提高代码可维护性、扩展性和复用性的关键技术之一。本文将通过探讨单例模式,一种最常用的设计模式,来揭示其在PHP中的应用及优势。单例模式确保一个类仅有一个实例,并提供一个全局访问点。通过实际案例,我们将展示如何在PHP项目中有效实现单例模式,以及如何利用这一模式优化资源配置和管理。无论是PHP初学者还是经验丰富的开发者,都能从本文中获得有价值的见解和技巧,进而提升自己的编程实践。
|
2月前
|
机器学习/深度学习 人工智能 算法
揭开深度学习与传统机器学习的神秘面纱:从理论差异到实战代码详解两者间的选择与应用策略全面解析
【10月更文挑战第10天】本文探讨了深度学习与传统机器学习的区别,通过图像识别和语音处理等领域的应用案例,展示了深度学习在自动特征学习和处理大规模数据方面的优势。文中还提供了一个Python代码示例,使用TensorFlow构建多层感知器(MLP)并与Scikit-learn中的逻辑回归模型进行对比,进一步说明了两者的不同特点。
74 2
|
2月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与实践
【10月更文挑战第9天】 策略模式是一种行为设计模式,它允许在运行时选择算法的行为。在PHP开发中,通过使用策略模式,我们可以轻松切换算法或逻辑处理方式而无需修改现有代码结构。本文将深入探讨策略模式的定义、结构以及如何在PHP中实现该模式,并通过实际案例展示其应用价值和优势。
31 1
|
2月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与应用
【10月更文挑战第8天】 在软件开发的浩瀚宇宙中,设计模式如同星辰指引,照亮了代码设计与架构的航道。本文旨在深入探索PHP语境下策略模式(Strategy Pattern)的精髓,不仅剖析其内核原理,还将其融入实战演练,让理论在实践中生根发芽。策略模式,作为解决“如何优雅地封装算法族”的答案,以其独特的灵活性与扩展性,赋予PHP应用以动态变换行为的能力,而无需牵动既有的类结构。
30 2
|
2月前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与实践
【10月更文挑战第12天】 在软件开发的世界中,设计模式是解决常见问题的最佳实践。它们不是具体的代码,而是一种编码和设计经验的总结。在PHP开发中,合理运用设计模式可以极大地提高代码的可维护性、扩展性和复用性。本文将深入探讨策略模式(Strategy Pattern)的原理、实现方式及其在PHP中的应用。通过具体示例,我们将展示如何利用策略模式来解耦算法与对象,从而让代码更加灵活和易于管理。
19 0
|
2月前
|
设计模式 存储 安全
PHP中的设计模式:单例模式的深入解析与实践
在PHP开发中,设计模式是提高代码可维护性、扩展性和重用性的关键技术之一。本文将深入探讨单例模式(Singleton Pattern)的原理、实现方式及其在PHP中的应用,同时通过实例展示如何在具体的项目场景中有效利用单例模式来管理和组织对象,确保全局唯一性的实现和最佳实践。

推荐镜像

更多