JavaScript职责链模式

JavaScript职责链模式

  • 1 什么是职责链模式
  • 2 举个例子
  • 3 用职责链模式重构代码
  • 4 灵活可拆分的职责链节点
  • 5 异步的职责链

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();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/164669.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++算法入门练习——树的带权路径长度

现有一棵n个结点的树(结点编号为从0到n-1,根结点为0号结点),每个结点有各自的权值w。 结点的路径长度是指,从根结点到该结点的边数;结点的带权路径长度是指,结点权值乘以结点的路径长度&#x…

RobotFramework框架之导入自己打包的python程序(十五)

引言 RobotFramework自动化框架(以下简称RF)之前文章我们讲了通过import第三方的library(RequestsLibrary等),在实际项目中第三方的包并不能满足我们的需要,此时我们可自己编写python模块(.py文…

springboot集成nacos并实现自动刷新

目录 1.说明 2.示例 3.自动刷新的注意点 1.说明 springboot项目中存在好多配置文件,比如配置数据信息,redis信息等等,配置文件可以直接放在代码,也可以放在像nacos这样的组件中,实现动态的管理,修改配置…

c++ list容器使用详解

list容器概念 list是一个双向链表容器,可高效地进行插入删除元素。 List 特点: list不可以随机存取元素,所以不支持at.(position)函数与[]操作符。可以对其迭代器执行,但是不能这样操作迭代器:it3使用时包含 #includ…

数据分析思维与模型:群组分析法

群组分析法,也称为群体分析法或集群分析法,是一种研究方法,用于分析和理解群体内的动态、行为模式、意见、决策过程等。这种方法在社会科学、心理学、市场研究、组织行为学等领域有广泛应用。它可以帮助研究人员或组织更好地理解特定群体的特…

算法通关村第十六关白银挑战——滑动窗口高频问题

关注微信公众号:怒码少年。回复关键词【电子书】,领取多本计算机相关电子书 公众号后台开启了咨询业务,欢迎大家向我提问,免费,为爱发电😎 大家好,我是怒码少年小码。 武汉今天真的超级冷&…

kubenetes-Service和EndpointSlice

一、Service 二、Endpoint Endpoint记录一个 Service 对应一组 Pod 的访问地址,一个 Service 只有一个 Endpoint资源。Endpoint资源会去监测Pod 集合,只要服务中的某个 Pod 发生变更,Endpoint 就会进行同步更新。 三、Service、Endpoint和 P…

Linux 网络:PMTUD 简介

文章目录 1. 前言2. Path MTU Discovery(PMTUD) 协议2.1 PMTUD 发现最小 MTU 的过程 3. Linux 的 PMTUD 简析3.1 创建 socket 时初始化 PMTUD 模式3.2 数据发送时 PMTUD 相关处理3.2.1 源头主机发送过程中 PMTU 处理3.2.2 转发过程中 PMTUD 处理 4. PMTUD 观察5. 参考链接 1. 前…

MattML

方法 作者未提供代码

SPASS-偏相关分析

基本概念 偏相关分析的任务就是在研究两个变量之间的线性相关关系时控制可能对其产生影响的变量,这种相关系数称为偏相关系数。偏相关系数的数值和简单相关系数的数值常常是不同的,在计算简单相关系数时,所有其他自变量不予考虑。 统计原理 控制一个变量和控制两个变量的偏…

【深度学习实验】注意力机制(一):注意力权重矩阵可视化(矩阵热图heatmap)

文章目录 一、实验介绍二、实验环境1. 配置虚拟环境2. 库版本介绍 三、实验内容0. 理论介绍a. 认知神经学中的注意力b. 注意力机制: 1. 注意力权重矩阵可视化(矩阵热图)a. 导入必要的库b. 可视化矩阵热图(show_heatmaps&#xff0…

公网访问全能知识库工具AFFINE,Notion的免费开源替代

文章目录 公网访问全能知识库工具AFFINE,Notion的免费开源替代品前言1. 使用Docker安装AFFINE2. 安装cpolar内网穿透工具3. 配置AFFINE公网访问地址4. 实现公网远程访问AFFINE 公网访问全能知识库工具AFFINE,Notion的免费开源替代品 前言 AFFiNE 是一个…

C++ 运算符重载详解

本篇内容来源于对c课堂上学习内容的记录 通过定义函数实现任意数据类型的运算 假设我们定义了一个复数类&#xff0c;想要实现两个复数的相加肯定不能直接使用“”运算符&#xff0c;我们可以通过自定义一个函数来实现这个功能&#xff1a; #include <iostream> using…

Backtrader绘图cerebro.plot报错问题的处理

Backtrader绘图cerebro.plot报错问题的处理 1.问题描述 在jupyter 中使用BackTrader &#xff0c;使用绘图功能时&#xff1a; cerebro.plot() 提示错误&#xff1a;ValueError: Axis limits cannot be NaN or Inf 由于backtrader 要求有7列数据&#xff0c;最后一列openint…

图像分类(六) 全面解读复现MobileNetV1-V3

MobileNetV1 前言 MobileNetV1网络是谷歌团队在2017年提出的&#xff0c;专注于移动端和嵌入设备的轻量级CNN网络&#xff0c;相比于传统的神经网络&#xff0c;在准确率小幅度降低的前提下大大减少模型的参数与运算量。相比于VGG16准确率减少0.9%&#xff0c;但模型的参数只…

【MySQL--->视图】

文章目录 [TOC](文章目录) 一、概念二、操作三、视图特性 一、概念 视图是一个由插叙结果组成的虚拟表,基于表查询结果得到的表叫做视图,被查询的表叫做基表.基表和视图进行更新操作会互相影响. 二、操作 创建视图 将dept和emp两个基表的查询结果作为视图 更新基表会影响视…

使用百度翻译API或腾讯翻译API做一个小翻译工具

前言 书到用时方恨少&#xff0c;只能临时抱佛脚。英文pdf看不懂&#xff0c;压根看不懂。正好有百度翻译API和腾讯翻译API&#xff0c;就利用两个API自己写一个简单的翻译工具&#xff0c;充分利用资源&#xff0c;用的也放心。 前期准备 关键肯定是两大厂的翻译API&#x…

算法通关村第十一关-青铜挑战理解位运算的规则

大家好我是苏麟 , 今天聊聊位运算 . 位运算规则 计算机采用的是二进制&#xff0c;二进制包括两个数码:0&#xff0c;1。在计算机的底层&#xff0c;一切运算都是基于位运算实现的&#xff0c;所以研究清整位运算可以加深我们对很多基础原理的理解程度。 在算法方面&#xf…

Python基础:错误和异常

在Python中的错误可&#xff08;至少&#xff09;被分为两种&#xff1a;语法错误和 异常&#xff0c;均是指在程序中发生的问题和意外情况。Python提供了异常处理机制&#xff0c;使程序能够更容易地应对这些问题。 1. 语法错误&#xff08;Syntax Error&#xff09; 语法错误…