JavaScript职责链模式

1 什么是职责链模式

职责链模式是一种行为型设计模式,它允许将请求沿着处理者链进行传递,直到其中一个处理者能够处理该请求为止,从而避免了请求的发送者和接收者之间的耦合关系

在职责链模式下,一系列可能会处理请求的对象被连接成一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象,我们把这些对象称为链中的节点。
在这里插入图片描述
该模式通常用于多个对象可以处理同一请求的情况,但不知道哪个对象才能最终处理请求,并且需要避免将请求发送到所有对象。

2 举个例子

假设在一个售卖手机的电商网站中,出现了两轮预定的活动,分别是交500元定金和200元定金,并且在交定金之后,订单已经生成,到了正式购买的阶段后,公司针对支付过定金的顾客有一定的优惠政策,已经支付过500元定金的顾客,会收到100元优惠券,支付过200元定金的顾客,可以收到50元优惠券,对于没有参加过预定活动的顾客,没有优惠券,而且在库存有限的情况下不一定可以买到。

在订单页面加载时,我们会收到以下几个参数:

  • orderType,表示订单类型(定金用户 or 普通购买用户),值为1时为500元定金用户,2时为200元定金用户,3时为普通购买用户
  • pay,表示用户是否已经支付过定金,值为true或者false,如果用户下过500元定金的订单,但是一直没有支付定金,也只能进入普通购买模式
  • stock,表示当前用于普通购买的手机库存数量,已经支付过定金的用户不计算

下面我们用代码表示这个流程:

var order = function (orderType, pay, stock) {
  // 500 元定金购买模式
  if (orderType === 1) {
    // 已支付定金
    if (pay === true) {
      console.log("500 元定金预购, 得到 100 优惠券");
    } else {
      // 未支付定金,降级到普通购买模式
      if (stock > 0) {
        // 用于普通购买的手机还有库存
        console.log("普通购买, 无优惠券");
      } else {
        console.log("手机库存不足");
      }
    }
    // 200 元定金购买模式
  } else if (orderType === 2) {
    // 如果已经支付过定金
    if (pay === true) {
      console.log("200 元定金预购, 得到 50 优惠券");
    } else {
      if (stock > 0) {
        console.log("普通购买, 无优惠券");
      } else {
        console.log("手机库存不足");
      }
    }
  } else if (orderType === 3) {
    if (stock > 0) {
      console.log("普通购买, 无优惠券");
    } else {
      console.log("手机库存不足");
    }
  }
};

order(1, true, 500); // 500 元定金预购, 得到 100 优惠券

上面这段代码,虽然实现了我们的业务,但是可能会经常修改,难以维护。

3 用职责链模式重构代码

现在我们采用职责链模式重构这段代码,先把500元订单、200元订单以及普通购买分成3个函数。

接下来把orderTypepaystock这3个字段当作参数传递给500元订单函数,如果该函数不符合处理条件,则把这个请求传递给后面的200元订单函数,如果200元订单函数依然不能处理该请求,则继续传递请求给普通购买函数,代码如下:

// 500元订单
var order500 = function (orderType, pay, stock) {
  if (orderType === 1 && pay === true) {
    console.log("500元定金预购, 得到100优惠券");
  } else {
    order200(orderType, pay, stock); // 将请求传递给200元订单
  }
};

// 200元订单
var order200 = function (orderType, pay, stock) {
  if (orderType === 2 && pay === true) {
    console.log("200元定金预购, 得到50优惠券");
  } else {
    orderNormal(orderType, pay, stock); // 将请求传递给普通订单
  }
};

// 普通购买订单
var orderNormal = function (orderType, pay, stock) {
  if (stock > 0) {
    console.log("普通购买, 无优惠券");
  } else {
    console.log("手机库存不足");
  }
};

// 测试结果:
order500(1, true, 500); // 500元定金预购, 得到100优惠券
order500(1, false, 500); // 普通购买, 无优惠券
order500(2, true, 500); // 200元定金预购, 得到50优惠券
order500(3, false, 500); // 普通购买, 无优惠券
order500(3, false, 0); // 手机库存不足

可以看到,执行结果和前面那个巨大的order函数完全一样,但是代码的结构已经清晰了很多,我们把去掉了许多嵌套的条件分支语句。但可以看到,请求在链条传递中的顺序非常僵硬,传递请求的代码被耦合在了业务函数之中。

这依然是违反开放—封闭原则的,如果有天我们要增加300元预订或者去掉200元预订,意味着就必须改动这些业务函数内部。

4 灵活可拆分的职责链节点

首先需要改写一下分别表示3种购买模式的节点函数,我们约定,如果某个节点不能处理请求,则返回一个特定的字符串'nextSuccessor'来表示该请求需要继续往后面传递:

var order500 = function (orderType, pay, stock) {
  if (orderType === 1 && pay === true) {
    console.log("500 元定金预购,得到 100 优惠券");
  } else {
    return "nextSuccessor";
  }
};

var order200 = function (orderType, pay, stock) {
  if (orderType === 2 && pay === true) {
    console.log("200 元定金预购,得到 50 优惠券");
  } else {
    return "nextSuccessor";
  }
};

var orderNormal = function (orderType, pay, stock) {
  if (stock > 0) {
    console.log("普通购买,无优惠券");
  } else {
    console.log("手机库存不足");
  }
};

接下来需要把函数包装进职责链节点,我们定义一个构造函数Chain,在new Chain的时候传递的参数即为需要被包装的函数,同时它还拥有一个实例属性this.successor,表示在链中的下一个节点。

var Chain = function (fn) {
  this.fn = fn;
  this.successor = null;
};

// 指定在链中的下一个节点
Chain.prototype.setNextSuccessor = function (successor) {
  return (this.successor = successor);
};

// 传递请求给某个节点
Chain.prototype.passRequest = function () {
  var ret = this.fn.apply(this, arguments);
  if (ret === "nextSuccessor") {
    return (
      this.successor &&
      this.successor.passRequest.apply(this.successor, arguments)
    );
  }
  return ret;
};

现在我们把3个订单函数分别包装成职责链的节点:

var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);

然后指定节点在职责链中的顺序:

chainOrder500.setNextSuccessor(chainOrder200);
chainOrder200.setNextSuccessor(chainOrderNormal);

最后把请求传递给第一个节点:

chainOrder500.passRequest(1, true, 500); // 500 元定金预购,得到 100 优惠券
chainOrder500.passRequest(2, true, 500); // 200 元定金预购,得到 50 优惠券
chainOrder500.passRequest(3, true, 500); // 普通购买,无优惠券
chainOrder500.passRequest(1, false, 0); // 手机库存不足

5 异步的职责链

在上面的代码中,我们让每个节点函数同步返回一个特定的值"nextSuccessor",来表示是否把请求传递给下一个节点。而在实际开发中,我们经常会遇到一些异步的问题,比如我们要在节点函数中发起一个 ajax异步请求,异步请求返回的结果才能决定是否继续在职责链中passRequest

这时要给Chain类再增加一个方法Chain.prototype.next,表示手动传递请求给职责链中的下一个节点:

Chain.prototype.next = function () {
  return (
    this.successor &&
    this.successor.passRequest.apply(this.successor, arguments)
  );
};

举个例子:

var Chain = function (fn) {
  this.fn = fn;
  this.successor = null;
};

// 指定在链中的下一个节点
Chain.prototype.setNextSuccessor = function (successor) {
  return (this.successor = successor);
};

// 传递请求给某个节点
Chain.prototype.passRequest = function () {
  var ret = this.fn.apply(this, arguments);
  if (ret === "nextSuccessor") {
    return (
      this.successor &&
      this.successor.passRequest.apply(this.successor, arguments)
    );
  }
  return ret;
};

Chain.prototype.next = function () {
  return (
    this.successor &&
    this.successor.passRequest.apply(this.successor, arguments)
  );
};

var fn1 = new Chain(function () {
  console.log(1);
  return "nextSuccessor";
});

var fn2 = new Chain(function () {
  console.log(2);
  var self = this;
  setTimeout(function () {
    self.next();
  }, 1000);
});

var fn3 = new Chain(function () {
  console.log(3);
});

fn1.setNextSuccessor(fn2).setNextSuccessor(fn3);
fn1.passRequest();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值