结构型模式 - 适配器模式

适配器模式

适配器模式是一种结构型设计模式,它能使接口不兼容的对象能够相互合作。

在这里插入图片描述

一:问题的引入

假如你正在开发一款股票市场监测程序,它会从不同来源下载 XML 格式的股票数据,然后向用户呈现出美观的图表。

在开发过程中,你决定在程序中整合一个第三方智能分析函数库。

但是遇到了一个问题,那就是分析函数库只兼容 JSON 格式的数据。

在这里插入图片描述
你无法 “直接” 使用分析函数库,因为它所需的输入数据格式与你的程序不兼容。

你可以修改程序库来支持 XML。

但是,这可能需要修改部分依赖该程序库的现有代码。甚至还有更糟糕的情况,你可能根本没有程序库的源代码,从而无法对其进行修改。

二:问题的解决方案

你可以创建一个适配器。这是一个特殊的对象,能够转换对象接口,使其能与其他对象进行交互。

适配器模式通过封装对象将复杂的转换过程隐藏于幕后。

被封装的对象甚至察觉不到适配器的存在。例如,你可以使用一个将所有数据转换为英制单位(如英尺和英里)的适配器封装运行于米和千米单位制中的对象。

适配器不仅可以转换不同格式的数据,其还有助于采用不同接口的对象之间的合作。它的运作方式如下:

  1. 适配器实现与其中一个现有对象兼容的接口。
  2. 现有对象可以使用该接口安全地调用适配器方法。
  3. 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。

有时你甚至可以创建一个双向适配器来实现双向转换调用。

在这里插入图片描述
让我们回到股票市场程序。

为了解决数据格式不兼容的问题,你可以为分析函数库中的每个类创建将XML转换为JSON格式的适配器,然后让客户端仅通过这些适配器来与函数库进行交流。

当某个适配器被调用时,它会将传入的XML数据转换为JSON结构, 并将其传递给被封装分析对象的相应方法

三:适配器模式的结构

1:对象适配器

实现时使用了构成原则:适配器实现了其中一个对象的接口,并对另一个对象进行封装。

所有流行的编程语言都可以实现适配器。

在这里插入图片描述

2:类适配器

这一实现使用了继承机制:适配器同时继承两个对象的接口。

请注意,这种方式仅能在支持多重继承的编程语言中实现,例如 C++。

在这里插入图片描述

四:应用场景

当你希望使用某个类, 但是其接口与其他代码不兼容时,可以使用适配器类。

适配器模式允许你创建一个中间层类, 其可作为代码与遗留类、 第三方类或提供怪异接口的类之间的转换器

如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法

但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性

你可以扩展每个子类,将缺少的功能添加到新的子类中。

但是,你必须在所有新子类中重复添加这些代码,这样会使得代码有坏味道。

将缺失功能添加到一个适配器类中是一种优雅得多的解决方案。

然后你可以将缺少功能的对象封装在适配器中,从而动态地获取所需功能。

如要这一点正常运作,目标类必须要有通用接口,适配器的成员变量应当遵循该通用接口。这种方式同装饰模式非常相似。

五:实现方式

  1. 确保至少有两个类的接口不兼容:
    • 一个无法修改 (通常是第三方、遗留系统或者存在众多已有依赖的类) 的功能性服务类。
    • 一个或多个将受益于使用服务类的客户端类。
  2. 声明客户端接口,描述客户端如何与服务交互。
  3. 创建遵循客户端接口的适配器类。所有方法暂时都为空。
  4. 在适配器类中添加一个成员变量用于保存对于服务对象的引用。
    • 通常情况下会通过构造函数对该成员变量进行初始化,但有时在调用其方法时将该变量传递给适配器会更方便。
  5. 依次实现适配器类客户端接口的所有方法。适配器会将实际工作委派给服务对象,自身只负责接口或数据格式的转换。
  6. 客户端必须通过客户端接口使用适配器。这样一来,你就可以在不影响客户端代码的情况下修改或扩展适配器。

六:优缺点

优点

  • 单一职责原则你可以将接口或数据转换代码从程序主要业务逻辑中分离。
  • 开闭原则。只要客户端代码通过客户端接口与适配器进行交互,你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。

缺点

  • 代码整体复杂度增加,因为你需要新增一系列接口和类。有时直接更改服务类使其与其他代码兼容会更简单。

七:与其他模式的关系

  • 桥接模式通常会于开发前期进行设计,使你能够将程序的各个部分独立开来以便开发。另一方面,适配器模式通常在已有程序中使用,让相互不兼容的类能很好地合作。
  • 适配器可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外,装饰还支持递归组合,适配器则无法实现。
  • 适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰模式则能为对象提供加强的接口。
  • 外观模式为现有对象定义了一个新接口, 适配器则会试图运用已有的接口。 适配器通常只封装一个对象,外观通常会作用于整个对象子系统上。
  • 桥接、 状态模式和策略模式(在某种程度上包括适配器)模式的接口非常相似。
    • 实际上,它们都基于组合模式——即将工作委派给其他对象
    • 不过也各自解决了不同的问题。
    • 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

八:代码示例

下列适配器模式演示基于经典的 “方钉和圆孔” 问题。

在这里插入图片描述
适配器假扮成一个圆钉,其半径等于方钉横截面对角线的一半(即能够容纳方钉的最小外接圆的半径)。

1:伪代码

// 假设你有两个接口相互兼容的类:圆孔(RoundHole)和圆钉(RoundPeg)。
class RoundHole is
    constructor RoundHole(radius) { …… }

    method getRadius() is
        // 返回孔的半径。

    method fits(peg: RoundPeg) is
        return this.getRadius() >= peg.getRadius()

class RoundPeg is
    constructor RoundPeg(radius) { …… }

    method getRadius() is
        // 返回钉子的半径。


// 但还有一个不兼容的类:方钉(SquarePeg)。
class SquarePeg is
    constructor SquarePeg(width) { …… }

    method getWidth() is
        // 返回方钉的宽度。


// 适配器类让你能够将方钉放入圆孔中。它会对 RoundPeg 类进行扩展,以接收适
// 配器对象作为圆钉。
class SquarePegAdapter extends RoundPeg is
    // 在实际情况中,适配器中会包含一个 SquarePeg 类的实例。
    private field peg: SquarePeg

    constructor SquarePegAdapter(peg: SquarePeg) is
        this.peg = peg

    method getRadius() is
        // 适配器会假扮为一个圆钉,其半径刚好能与适配器实际封装的方钉搭配
        // 起来。
        return peg.getWidth() * Math.sqrt(2) / 2


// 客户端代码中的某个位置。
hole = new RoundHole(5)
rpeg = new RoundPeg(5)
hole.fits(rpeg) // true

small_sqpeg = new SquarePeg(5)
large_sqpeg = new SquarePeg(10)
hole.fits(small_sqpeg) // 此处无法编译(类型不一致)。

small_sqpeg_adapter = new SquarePegAdapter(small_sqpeg)
large_sqpeg_adapter = new SquarePegAdapter(large_sqpeg)
hole.fits(small_sqpeg_adapter) // true
hole.fits(large_sqpeg_adapter) // false

2:项目结构

在这里插入图片描述

3:代码实现

round -> RoundHole

package com.cui.commonboot.design_pattern.struct_pattern.adapter_pattern.round;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * <p>
 * 功能描述:圆孔
 * </p>
 *
 * @author cui haida
 * @date 2023/08/28/19:52
 */
@AllArgsConstructor
@Getter
public class RoundHole {
    /** 圆孔的半径 **/
    private double radius;

    public boolean fits(RoundPeg peg) {
        boolean result;
        result = (this.radius >= peg.getRadius());
        return result;
    }
}

round -> RoundPeg

package com.cui.commonboot.design_pattern.struct_pattern.adapter_pattern.round;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
 * <p>
 * 功能描述:圆钉
 * </p>
 *
 * @author cui haida
 * @date 2023/08/28/19:54
 */
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class RoundPeg {
    /** 圆钉的半径 **/
    private double radius;
}

square -> SquarePeg

package com.cui.commonboot.design_pattern.struct_pattern.adapter_pattern.square;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * <p>
 * 功能描述:方钉
 * </p>
 *
 * @author cui haida
 * @date 2023/08/28/19:56
 */
@AllArgsConstructor
@Getter
public class SquarePeg {
    private double width;

    /** 面积 **/
    public double getSquare() {
        return Math.pow(width, 2);
    }
}

adpaters -> SquarePegAdapter

package com.cui.commonboot.design_pattern.struct_pattern.adapter_pattern.adapters;

import com.cui.commonboot.design_pattern.struct_pattern.adapter_pattern.round.RoundPeg;
import com.cui.commonboot.design_pattern.struct_pattern.adapter_pattern.square.SquarePeg;

import java.util.Map;

/**
 * <p>
 * 功能描述:
 * </p>
 *
 * @author cui haida
 * @date 2023/08/28/19:57
 */
public class SquarePegAdapter extends RoundPeg {
    private SquarePeg squarePeg;

    public SquarePegAdapter(SquarePeg squarePeg) {
        this.squarePeg = squarePeg;
    }

    /**
     * 在圆钉看来方钉的半径 -> 外接圆的半径
     * @return 外接圆半径
     */
    @Override
    public double getRadius() {
        return Math.sqrt(Math.pow((squarePeg.getWidth() / 2), 2) * 2);
    }
}

Demo

package com.cui.commonboot.design_pattern.struct_pattern.adapter_pattern;

import com.cui.commonboot.design_pattern.struct_pattern.adapter_pattern.adapters.SquarePegAdapter;
import com.cui.commonboot.design_pattern.struct_pattern.adapter_pattern.round.RoundHole;
import com.cui.commonboot.design_pattern.struct_pattern.adapter_pattern.round.RoundPeg;
import com.cui.commonboot.design_pattern.struct_pattern.adapter_pattern.square.SquarePeg;

/**
 * <p>
 * 功能描述:客户端测试类
 * </p>
 *
 * @author cui haida
 * @date 2023/08/28/19:49
 */
public class Demo {
    public static void main(String[] args) {
        RoundHole hole = new RoundHole(5);
        RoundPeg rpeg = new RoundPeg(5);
        if (hole.fits(rpeg)) {
            System.out.println("Round peg r5 fits round hole r5.");
        }

        SquarePeg smallSqPeg = new SquarePeg(2);
        SquarePeg largeSqPeg = new SquarePeg(20);

        SquarePegAdapter smallSqPegAdapter = new SquarePegAdapter(smallSqPeg);
        SquarePegAdapter largeSqPegAdapter = new SquarePegAdapter(largeSqPeg);
        if (hole.fits(smallSqPegAdapter)) {
            System.out.println("Square peg w2 fits round hole r5.");
        }
        if (!hole.fits(largeSqPegAdapter)) {
            System.out.println("Square peg w20 does not fit into round hole r5.");
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值