JavaScript设计模式-结构型-外观模式

2021-07-01 JavaScript设计模式 阅读 768 次

概要

外观模式是一种结构型设计模式, 能为程序库、 框架或其他复杂类提供一个简单的接口。

JavaScript设计模式-结构型-外观模式-黄继鹏博客

问题

假设你必须在代码中使用某个复杂的库或框架中的众多对象。 正常情况下, 你需要负责所有对象的初始化工作、 管理其依赖关系并按正确的顺序执行方法等。

最终, 程序中类的业务逻辑将与第三方类的实现细节紧密耦合, 使得理解和维护代码的工作很难进行。

解决方案

外观类为包含许多活动部件的复杂子系统提供一个简单的接口。 与直接调用子系统相比, 外观提供的功能可能比较有限, 但它却包含了客户端真正关心的功能。

如果你的程序需要与包含几十种功能的复杂库整合, 但只需使用其中非常少的功能, 那么使用外观模式会非常方便。

比如,浏览器通过接口stream流来完成文件下载,会用到不同文件类型的逻辑处理,还有下载动作以及其他复杂步骤,客户端要完成下载这个stream流文件,需要依次按正确步骤调用这些复杂方法。

我们以创建一个封装所需功能并隐藏其他代码的外观类, 从而避免客户端直接与较多的负责类进行交互。

该结构还能将未来框架升级或更换所造成的影响最小化, 因为你只需修改程序中外观方法的实现即可。

外观模式模式结构图

JavaScript设计模式-结构型-外观模式-黄继鹏博客

  1. 外观 (Facade) 提供了一种访问特定子系统功能的便捷方式, 其了解如何重定向客户端请求, 知晓如何操作一切活动部件。

  2. 创建附加外观 (Additional Facade) 类可以避免多种不相关的功能污染单一外观, 使其变成又一个复杂结构。 客户端和其他外观都可使用附加外观。

  3. 复杂子系统 (Complex Subsystem) 由数十个不同对象构成。 如果要用这些对象完成有意义的工作, 你必须深入了解子系统的实现细节, 比如按照正确顺序初始化对象和为其提供正确格式的数据。

    子系统类不会意识到外观的存在, 它们在系统内运作并且相互之间可直接进行交互。

  4. 客户端 (Client) 使用外观代替对子系统对象的直接调用。

举例

案例一,下载不同类型的stream文件

在本例中, 外观模式简化了客户端与复杂文件下载类之间的交互。

/**
 * 这里有复杂功能和一些独立板块类。我们不知晓其中的代码,因此无法
 * 对其进行简化。
 * buffer处理为xlsx Blob
 */
class XlsxReaderBlob {
  constructor(buffer) {
    this.blob = new Blob([buffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
  }
}

/**
 * buffer处理为image Blob
 */
class ImageReaderBlob {
  constructor(buffer) {
    this.blob = new Blob([buffer], { type: 'image/*' })
  }
}

/**
 * 处理blob文件下载
 */
class DownloadMixer {
  constructor(fileName, blob) {
    this.fileName = fileName
    this.blob = blob
  }
  exec() {
    let aLink = document.createElement('a')
    aLink.href = URL.createObjectURL(this.blob)
    aLink.setAttribute('download', this.fileName)
    document.body.appendChild(aLink)
    aLink.click()
    document.body.removeChild(aLink)
  }
}

/**
 * 为了将功能的复杂性隐藏在一个简单接口背后,创建了一个外观类。它是在
 * 功能性和简洁性之间做出的权衡。
 * 流转换器
 */
class StreamConverter {
  downLoad(buffer, fileName, fileType) {
    let blobSource
    if (fileType = 'xlsx') {
      blobSource = new XlsxReaderBlob(buffer)
    } else if (fileType == 'image') {
      blobSource = new ImageReaderBlob(buffer)
    }
    let link = new DownloadMixer(fileName, blobSource.blob)
    link.exec()
  }
}

/**
 * 应用程序的类并不依赖于复杂独立类中成千上万的类。同样,如果你决定更换功能板块,
 * 那只需重写外观类即可
 * [clientCode 客户端]
 */
function clientCode() {
  let fileTarget = new StreamConverter()
  fileTarget.downLoad('buffer', '1.jpg', 'xlsx')
}

clientCode()

output

download image type file success.

总结

优缺点

  • 优点
    • 松散耦合:外观模式松散了客户端与子复杂类之间的耦合关系,利于模块更容易扩展和维护,未来更换依赖复杂类, 只需修改程序中外观方法的实现即可。
    • 简单易用;客户端不需要了解子系统\子复杂类内部各模块的具体实现,只要跟外观层交互,委托外观层去实现即可。
  • 缺点
    • 最大缺点是违背了开闭原则,当增加或移除子系统时需要修改外观类。

适合应用场景

  • 为一个复杂系统提供一个给外部访问的简单接口。
  • 当客户端与多个子系统之间存在很大的依赖性时,引入外观模式将客户端与子系统解耦。
  • 在分层结构系统构建时,引入外观模式定义系统中每层入口点,层与层之间通过外观建立联系,降低层之间的耦合度。
0条评论
...