设计模式的 js 实现 (2)--装饰器模式

Duang2019-09-23 11:07:08架构设计JavaScript设计模式

提示

了解设计模式是学习一切软件架构设计的基础,大到一个项目的整体框架设计,小到一个功能函数的优化,都有着重要意义。《代码大全》中将设计模式共分为了 23 类,分别为:

  1. 创建型模式(5 种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
  2. 结构型模式(7 种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
  3. 行为型模式(11 种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

接下来我将针对其中常用的几种设计模式进行解读与实现,供大家参考。

介绍

上一篇中我们提到了装饰器模式,这也是一种非常实用的设计模式,主要的特点是非侵入式,能让组件功能能被更好的解耦和复用,解决了不同组件之间共享某些相同功能的难题。有时候我们并不关心函数的内部实现,仅仅想做功能增强,装饰器模式就能给我们带来良好的可读性的同时,减少我们修改需求的成本。

在 js 中,我们可以构造一个装饰器函数,接收任何一个组件函数,返回一个增强功能的新组件,原组件其他功能不受影响,并且当该增强功能不再使用时,只需要在调用处去除包裹的装饰器函数即可,这样可插拔式的功能体验可使得代码变得简单而优雅。

修饰函数的装饰器实现

正是因为装饰器模式带来的种种好处,在 es7 标准中已经正式引入了装饰器特性,标志符为 @。我们可以设定如下情况:设计一个通用型的日志补丁,能够输出组件中某些方法的调用日志的功能,我们可以这样写:

以下代码请务必在支持 es7 的环境下运行,如何使用请自行查阅 babel 相关文档

class Component {
  @decorateLog
  addNum(num1, num2) {
    return num1 + num2;
  }
}

function decorateLog(target, name, descriptor) {
  return {
    value: function(...args) {
      console.log(`${name}.arguments:`, args);
      return descriptor.value.apply(this, args);
    }
  };
}

const comp = new Component();
const res = comp.addNum(1, 2);
console.log(`answer is:`, res); // addNum.arguments:[1, 2] answer is: 3

不难看出,装饰器函数接收三个参数:被修饰对象,被修饰对象名,以及该对象属性描述符(参考Object.defineProperty中的descriptor属性),返回新的被修饰对象属性描述符。

修饰类的装饰器实现

装饰器不仅能被用于修饰类属性,甚至能够直接修饰类本身,增强类功能。这个特性也十分实用。本质上,装饰器的行为就是一个高阶函数,其作用全等于以下用法:

function decorator(target) {
  // ...
}

class Component {}
Component = decorator(Component) || Component;

之前在写 redux 相关的文章时也顺口提到过,connect 这个高阶组件也能够用装饰器模式来实现,使得导出类变得更美观且易读。

这是不使用装饰器的 connect 组件写法,高阶函数显得冗长不易读,export 导出物不够明显与直观。

class Component extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(Component);

使用装饰器后,写法变成了这样

@connect(mapStateToProps, mapDispatchToProps)
export default class Component extends React.Component {}

我们也参照上述写法,来实现自己的类装饰器。假设这样一种情况,我们需要对每一个类组件增添一个获取作者名的通用方法,那我们可以这样来做:

@decorateAuthor
class Component {
  // ... 其他属性
}

function decorateAuthor(target) {
  target.prototype.getAuthor = () => {
    return 'synccheng';
  }
}

const comp = new Component();
console.log(comp.getAuthor())

需要注意的一点的是,在 js 中,装饰器只能用于类和类中的方法,不能用于函数。这是因为,装饰器函数是在编译时执行,而不是在运行时执行。因此,若之间在普通函数中使用会存在函数提升的问题,即先定义函数名称,并不赋值,在被修饰函数声明时只能取到 undefined 的装饰器值,导致调用结果与预期不符。

总而言之,在适当的时机使用装饰器模式能帮我们得到一份优雅、易读、可插拔式的代码体验,使用装饰器模式,不需要深入理解原有的代码逻辑就能直接开发新特性,在对老代码进行功能增强的时候,有着事半功倍的效果。

最后更新时间 2/7/2025, 12:13:03 PM
Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v2.15.8