首页 设计模式 01 — 职责链模式
文章
取消

设计模式 01 — 职责链模式

1. 定义

职责链模式的定义是:使多个对象都有机会处理请求,从而避免请求的发送者接受者之间的耦合关系:

Request –> A –> B –> C –> D

2. 面向对象的职责链

2.1 构造函数 Chain

首先定义一个构造函数 Chain:

1
2
3
4
5
6
7
// 构造函数 Chain
// fn 为需要被包装的函数
// nextNode 为链中的下一个节点
var Chain = function(fn) {
  this.fn = fn;
  this.nextNode = null;
};

2.2 Chain.prototype

给 Chain 的原型添加方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 指定链中的下一个节点
Chain.prototype.setNextNode = function(nextNode) {
  return (this.nextNode = nextNode); //返回下一个节点
};
// 处理并传递请求
Chain.prototype.handleRequest = function() {
  var ret = this.fn.apply(this, arguments);
  if (ret === 'NEXT_NODE') {
    return (
      this.nextNode &&
      this.nextNode.handleRequest.apply(this.nextNode, arguments)
    );
  }
  return ret;
};

2.3 异步职责链

给 Chain 的原型添加方法:

1
2
3
4
5
6
// 异步职责链,手动把请求传递给职责链中的下一个节点
Chain.prototype.next = function() {
  return (
    this.nextNode && this.nextNode.handleRequest.apply(this.nextNode, arguments)
  );
};

3. 使用闭包实现职责链

3.1 _after 方法

首先给出 Function.prototype._after 方法:

  • 接受一个函数 fn 作为参数,返回一个函数;
  • 此函数先执行函数自身
    • 能处理,返回运行结果;
    • 不能处理(返回 ‘NEXT_NODE’),则返回 fn 的运行结果。
1
2
3
4
5
6
7
8
9
10
Function.prototype._after = function(fn) {
  var _self = this;
  return function() {
    var ret = _self.apply(this, arguments);
    if (ret === 'NEXT_NODE') {
      return fn.apply(this, arguments);
    }
    return ret;
  };
};

3.2 使用案例

有一个预约会议的表单页面,点击预约按钮后,会根据当前表单的状态,预约本地会议、Umeeting 会议、视频会议和混合会议四种会议。

3.2.1 初始函数

这个创建会议的函数可能是这样的:

1
2
3
4
5
6
7
8
9
10
11
var createMeeting = function(meeting) {
  if (meeting.type === 1) {
    // 创建本地会议...
  } else if (meeting.type === 3) {
    // 创建Umeeting会议...
  } else if (meeting.type === 0 && !meeting.isMixed) {
    // 创建视频会议...
  } else if (meeting.type === 0 && meeting.isMixed) {
    // 创建混合会议...
  }
};

3.2.2 使用职责链重构

分解 createMeeting 函数,每个会议的创建过程单独封装成一个函数,使其粒度更加细小。再根据职责链的定义,分析出此案例的模型:

点击预约按钮(发起请求) --> createLocalMeeting --> createUmeeting --> createVideoMeeting --> createMixedMeeting 。

当某个节点无法处理请求时,会传递给此链条中的下一个节点。

使用职责链重构后:

1
2
3
4
5
// 创建会议(职责链模式)
var createMeeting = createLocalMeeting
  ._after(createUmeeting)
  ._after(createVideoMeeting)
  ._after(createMixedMeeting);

创建本地会议:

1
2
3
4
5
6
var createLocalMeeting = function(meeting) {
  if (meeting.type !== 1) {
    return 'NEXT_NODE';
  }
  // 创建本地会议的逻辑...
};

创建Umeeting会议:

1
2
3
4
5
6
var createUmeeting = function(meeting) {
  if (meeting.type !== 3) {
    return 'NEXT_NODE';
  }
  // 创建Umeeting会议的逻辑...
};

创建视频会议:

1
2
3
4
5
6
var createVideoMeeting = function(meeting) {
  if (meeting.type !== 0 || meeting.isMixed) {
    return 'NEXT_NODE';
  }
  // 创建视频会议的逻辑...
};

创建混合会议:

1
2
3
4
5
6
var createMixedMeeting = function(meeting) {
  if (meeting.type !== 0 || meeting.isMixed) {
    return 'NEXT_NODE';
  }
  // 创建混合会议的逻辑...
};

重构之后的好处:

  1. 粒度更小的函数更易于阅读、维护和复写。
  2. 良好的函数命名本身就起到了注释的作用。
  3. 职责链中的每个节点之间已解耦,不会相互影响。可扩展性更强:
    • 当不需要某个类型的会议时,直接在链条中取消创建此类型会议的函数即可。
    • 当需要增加某个类型会议时,直接在链条的任意位置插入新函数即可,不需要去修改其他类型会议的创建函数。
  4. 每个函数(对象)更加符合单一职责原则。

4. 使用闭包实现异步职责链

Vue Router 的导航守卫 API 就是一个异步职责链:

1
2
3
router.beforeEach((to, from, next) => {
  /* 必须调用 `next` */
})

当我们调用 next() 时,才会进行管道中的下一个钩子。

现在我们利用闭包来实现一个简单的导航守卫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const router = (function () {
  const fnsArr = [];
  return {
    beforeEach(fn) {
      fnsArr.push(fn);
    },
    navigate(to, from) {
      // 职责链中所有回调运行完成后运行
      const fnComplete = () => {
        console.log('运行完成');
      };
      // 递归生成职责链
      const chain = fnsArr.reduceRight((acc, fn) => {
        acc = createChain(fn, to, from, acc);
        return acc;
      }, fnComplete);
      // 运行
      chain();
    },
  };
  /**
   * 利用闭包生成职责链
   * @param {Function} fn 当前节点
   * @param {Object} to 进入的路由
   * @param {Object} from 离开的路由
   * @param {Function} nextFn 后一个节点
   */
  function createChain(fn, to, from, nextFn) {
    if (!fn) return nextFn;
    return function () {
      fn.call(fn, to, from, nextFn);
    };
  }
})();

测试代码: JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
router.beforeEach((to, from, next) => {
  console.log('节点1');
  console.log(`to: ${JSON.stringify(to)} from: ${JSON.stringify(from)}`);
  next();
});

router.beforeEach((to, from, next) => {
  // 模拟异步
  setTimeout(() => {
    console.log('节点2');
    console.log(`to: ${JSON.stringify(to)} from: ${JSON.stringify(from)}`);
    next();
  }, 2000);
});

router.beforeEach((to, from, next) => {
  console.log('节点3');
  console.log(`to: ${JSON.stringify(to)} from: ${JSON.stringify(from)}`);
  next();
});

// 开始导航
router.navigate({ name: 'to' }, { name: 'from' });

调试窗口将打印:

1
2
3
4
5
6
7
节点1
to: {"name":"to"} from: {"name":"from"}
节点2
to: {"name":"to"} from: {"name":"from"}
节点3
to: {"name":"to"} from: {"name":"from"}
运行完成
本文由作者按照 CC BY 4.0 进行授权