转载来自博客园:https://www.cnblogs.com/longbensong/p/17230968.html

定义

  如果希望动态给某个类添加一些属性或者方法,但是你又不希望这个类派生的对象受到影响,那么装饰器模式就可以给你带来这样的体验。

它的定义就是在不改变原对象的基础上,通过对其进行包装拓展,使得原有对象可以动态具有更多功能,从而满足用户的更复杂需求

举个例子,一部手机,你可以买各种花里胡哨的手机壳等,这些手机壳其实就起到了装饰的作用,对手机本身的功能没有影响。

那么装饰器模式的特点就来了:

  1. 不影响原有功能
  2. 可同时装饰多个

js模拟装饰模式

向一个现有对象添加新的功能,同时又不改变其结构。

如我在跑步,但是我想一边跑步一边听歌。我们通常很快速的下写如下代码

/*
 如跑步时我想听音乐
*/
function run() {
  console.log('joel run')
}

// 一般改成如下:
function run() {
  console.log('joel run')
  // 听音乐代码 ...
}
// 或者把听音乐抽成一个方法
function music() {
  console.log('听歌')
}
function run() {
  console.log('joel run')
  music()
}
// 以上都违反来关闭原则

上面的代码都需要在原有的代码上面做调整,这样违反了关闭原则,按照装饰模式的定义,我们可以用装饰器模式扩展我们的跑步方法。

如下把上面的代码改成符合装饰模式思想

/***************************** 1、最简单的装饰 **************************/
// 1.1 入口套一层函数
function decoratorRun (fn) {
  fn()
  music()
}

decoratorRun(run)

// 1.2 中间变量保存引用
function run () {
  console.log('joel run')
}
function music() {
  console.log('听歌')
}
let _run = run;
run = function () {
  _run()
  music()
}
run()
// 上面两种都是固定了调用的位置不太灵活,并没有当成一个工具函数来使用

/***************************** 2、工具函数 **************************/
// 装饰fn函数,利用闭包特点
const decoratorRun1 = function (fn, afterFn) {
  return function (...args) {
    const res = fn.apply(this, args)
    afterFn.apply(this, args );
    return res;
  }
}
function run() {
  console.log('joel run')
}
function music(name) {
  console.log(`我正在听${name},很好听。`)
}
const runAndMusic = decoratorRun1(run,music);
runAndMusic('孤勇者');

/************************* 3、原型链, 把入口挂载到Function原型上*********/
Function.prototype.before = function (beforeFn) {
  const _self = this;
  return function (...args) {
    beforeFn.apply(this, args);
    return _self.apply(this, args);
  }
}

Function.prototype.after = function (afterFn) {
  const _self = this;
  return function (...args) {
    const result = _self.apply(this, args);
    afterFn.apply(this, args);
    return result;
  }
}
function run() {
  console.log('joel run')
}
function music(name) {
  console.log(`我正在听${name},很好听。`)
}
const runAndMusic1 = run.after(music)
runAndMusic1('孤勇者')

/***************************** 4、es7+ 装饰器写法 **************************/
@testable
class MyTestableClass {
  // ...
}

function testable(target) {
  target.isTestable = true;
}

MyTestableClass.isTestable // true

js 中的装饰器

在最新版本的es next 中,引入了 Decorator (装饰器的语法),从语言语法层面帮助我们快速的扩展我们的类(class)的功能。

function testAble(isTestable) {
    return function(target) {
        target.isTestable = isTestable;
    }
}

@testAble(true)
class MyTestClass {}
MyTestClass.isTestable; // true

@testAble(false)
class MyTestClass1 {}
MyTestClass1.isTestable; // false

你要知道的AOP概念

在上面模拟装饰器的时候,把函数挂载到Function原型上,有那么一点点AOP的思路。

AOP为Aspect Oriented Programming的缩写,指面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。

AOP 是一种编程范式,通俗的可以理解为这种在运行时,编译时,类和方法加载时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

如上图从横向角度来分析,业务1-3都需要日志,安全、其他等这种与业务无关的代码逻辑,这个时候我们把这种代码抽离出来,在动态的插入的技术就是面向切面编程。

AOP主要把业务逻辑无关的功能进行抽离,这些与业务逻辑无关的内容如日志打印、数据统计、异常处理、权限控制等各个部分进行隔离,在通过动态注入的方式注入到业务代码中。这样做既保证了业务内部高内聚,模块之间低耦合,方便管理与业务无关的模块,有利于未来的可操作性和可维护性。

面向切面编程(AOP)是一种通过横切关注点(Cross-cutting Concerns)分离来增强代码模块性的方法,它能够在不修改业务主体代码的情况下,对它添加额外的行为。

优点是:这样的做法,对原有代码毫无入侵性

AOP场景

AOP采取横向思考的思维,横向抽取机制,取代了传统纵向继承体系重复性代码的编写方式(例如性能监视、事务管理、安全检查、缓存、日志记录等)。

在软件系统设计的时候,我们需要把一个大的系统按照业务功能进行拆分,做到高内聚、低耦合。

但是呢,拆分之后会产生一些通用性的东西,比如日志,安全,事务,性能统计等,这些非功能性需求。

  1. 记录日志
  2. 监控方法运行时间 (监控性能)
  3. 权限控制
  4. 缓存优化 (第一次调用查询数据库,将查询结果放入内存对象, 第二次调用, 直接从内存对象返回,不需要查询数据库 )
  5. 事务管理 (调用方法前开启事务, 调用方法后提交或者回滚、关闭事务 )

AOP、装饰器模式

 AOP 与装饰器都是解决一样的问题,隔离业务逻辑无关的代码快。

装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为如有日志、性能测试、事务处理等,在 JavaScript 中,我们可以通过装饰者模式来实现 AOP,但是两者并不是一个维度的概念。 AOP 是一种编程范式,而装饰者是一种设计模式。 

小结

  1. 装饰者模式非常适合给业务代码附加非业务相关功能
  2. 装饰者模式非常适合无痛扩展别人的代码
  3. 装饰器模式还真的可以让代码变得优雅