JavaScript设计模式-结构型-适配器模式

2021-05-19 JavaScript设计模式 阅读 587 次

概要

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

适配器模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本 由于接口不兼容而不能工作的两个软件实体可以一起工作。

JavaScript设计模式-结构型-适配器模式-黄继鹏博客

问题

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

在开发过程中, 我们决定在程序中整合一个第三方智能分析函数库。 但是遇到了一个问题, 那就是分析函数库只兼容 JSON 格式的数据。

JavaScript设计模式-结构型-适配器模式-黄继鹏博客

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

解决方案

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

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

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

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

解决数据格式不兼容的问题, 你可以为分析函数库中的每个类创建将 XML 转换为 JSON 格式的适配器, 然后让客户端仅通过这些适配器来与函数库进行交流。 当某个适配器被调用时, 它会将传入的 XML 数据转换为 JSON 结构, 并将其传递给被封装分析对象的相应方法。

适配器模式结构图

JavaScript设计模式-结构型-适配器模式-黄继鹏博客

  1. 客户端(Client):是包含当前程序业务逻辑的类,支持所有遵循 Target 接口的类。

  2. 目标抽象类(Target)描述了其他类与客户端代码合作时必须遵循的协议。

  3. 适配者类(Adaptee)中有一些功能类 (通常来自第三方或遗留系统)。 客户端与其接口不兼容, 因此无法直接调用其功能。在客户端代码可以使用它之前,Adaptee 需要进行一些调整。

  4. 适配器类(Adapter)是一个可以同时与客户端和适配者交互的类: 它在实现客户端接口的同时封装了适配者对象。 适配器接受客户端通过适配器接口发起的调用, 并将其转换为适用于被封装适配者对象的调用。适配器类 使 适配者类 的接口与 目标 的接口兼容。

  5. 客户端代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。 因此, 你可以向程序中添加新类型的适配器而无需修改已有代码。 这在适配者类的接口被更改或替换时很有用: 你无需修改客户端代码就可以创建新的适配器类。

举例

程序开发中,需求要接入百度地图功能。baiduMap 提供了 show 方法来初始化地图。

因为种种原因,我们需要接入谷歌地图,而谷歌地图不提供show方法,它提供的是display方法。这里的两种地图都来自于第三方,第三方的接口方法并不在我们自己的控制范围之内。正常情况下我们都不应该去改动它。此时我们可以通过增加 Adapter 来解决问题:

/**
 * Target 定义了客户端代码使用的特定于域的接口。
 */
class TargetBaiduMap {
  show() {
    return 'create a baidu global map.'
  }
}

/**
 * 适配者类 Adaptee 包含一些有用的行为,但其接口与现有客户端代码不兼容。在客户端代码可以使用它之前,Adaptee 需要进行一些调整。
 */
class GoogleMapAdaptee {
  display() {
    return 'create a google global map.'
  }
}

/**
 * 适配器类 使 Adaptee 的接口与 Target 的接口兼容
 */
class GoogleMapAdapter extends TargetBaiduMap {
  constructor(map) {
    super()
    this.map = map
  }

  show() {
    return this.map.display()
  }
}

/**
 * [clientRenderMap 客户端代码支持所有遵循 Target 接口的类。]
 */
function clientRenderMap(map) {
  console.log(map.show())
}

const baiduMap = new TargetBaiduMap()
clientRenderMap(baiduMap)

const googleMap = new GoogleMapAdaptee()
const adapter = new GoogleMapAdapter(googleMap)
clientRenderMap(adapter)

output

"create a baidu global map."
"create a google global map."

总结

优缺点

  • 优点

    • 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,无须修改原有结构,符合“单一原则”。
    • 灵活性和扩展性都非常好,通过更换适配器,也可以在不修改原有代码的基础上增加新的适配器类,符合“开闭原则”。
  • 缺点

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

适合应用场景

  • 适配器模式主要用来解决两个已有接口之间不匹配的问题,它不考虑这些接口是怎样实现的,也不考虑它们将来可能会如何演化。适配器模式不需要改变已有的接口,就能够使它们协同作用。
0条评论
...