JavaScript职责链模式

news2024/10/7 14:32:36

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Flutter笔记:缩放手势

Flutter笔记 缩放手势 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article/details/134485138 目 录 1. 概述2. 缩放手…

QGroundControl源码编译的三种方法

1.使用QtCreator编译: 下载qgroundcontrol源码 https://github.com/mavlink/qgroundcontrol.git 克隆 同步子模块 使用打开qgroundcontrol.pro 打开前要求先安装qt 5.15.2

【神印王座】月夜大尺度诱惑,皓晨潜入月魔宫,枫秀降临男扮女装

Hello,小伙伴们,我是拾荒君。 为了能安全回到联盟,龙皓晨决定让月夜商队护送他们,这也是他们目前处境更快更安全回到人类境地的方法。于是,龙皓晨只身一人去寻找月夜,此次执行的任务完全超出龙皓晨的掌握之外&#xf…

数据结构与算法-图

图 🎈2.图的存储结构📖2.4.2邻接表的存储✅2.4.2.1逆邻接表✅2.4.2.2邻接表存储结构的定义✅2.4.2.3邻接表存储结构的类定义✅2.4.2.4创建n个顶点m条边的无向网✅2.4.2.5创建n个顶点m条边的有向网✅2.4.2.6定位操作-查找定点信息在顶点数组中的下标✅2.4…

操作系统:操作系统教程第六版(骆斌、葛季栋、费翔林)习题一计算机操作系统概述

目录 前言1. 思考题2. 应用题 前言 本系列文章是针对操作系统教程第六版(骆斌、葛季栋、费翔林)的习题解答,其中简答题部分为博主自己搜索整理的,错漏之处在所难免。应用题部分有答案为依据。 1. 思考题 (1&#xf…

算数通关村第十一关-白银挑战位运算高频题

位移的运算 位1的个数 描述 : 编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 1 的个数(也被称为汉明重量)。 题目 : LeetCode 191.位1的个数 191. 位1的个数…

pr出现由于找不到msvcp110.dll,无法继续执行代码怎么办?如何修复

为什么我们在打开运行电脑软件会出现msvcr110.dll无法继续执行此代码的问题呢?因为msvcr110.dll是Microsoft Visual C Redistributable Package for Visual Studio 2013的一个动态链接库。它是一个重要的组件,用于帮助游戏和软件运行。如果某个程序是用它…

程序的编译链接以及装载

目录 一、预处理 二、编译 三、汇编 四、链接 五、装载 一、预处理 读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理, 伪指令主要包括以下五个方面: 宏定义指令,如#define Name Token…

BUUCTF [BJDCTF2020]你猜我是个啥 1

BUUCTF:https://buuoj.cn/challenges 题目描述: 来源:https://github.com/BjdsecCA/BJDCTF2020 密文: 下载附件,得到一个zip压缩包。 解题思路: 1、尝试解压压缩包,提示“attachment_10.zip”不是压缩文…

安装宝塔,使用宝塔自动部署

这里用的是华为云服务器,其实也就是普普通通的一个linux操作系统 首先我们来到宝塔官网地址 宝塔面板下载,免费全能的服务器运维软件 (bt.cn) 翻到下面,会有使用脚本安装的命令。 yum install -y wget && wget -O install.sh https:/…

springboot集成nacos并实现自动刷新

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

Springboot 对于数据库字段加密方案(此方案是对字符串处理的方案)

背景:在erp开发中,有些用户比较敏感数据库里的数据比较敏感,系统给用户部署后,公司也不想让任何人看到数据,所以就有了数据库字段加密方案。 技术 spring boot mybatisplus 3.3.1 mybatisplus 实际提供了 字段加密方案 第一 他…

Flutter笔记:拖拽手势

Flutter笔记 拖拽手势 作者:李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 :291148484163.com 本文地址:https://blog.csdn.net/qq_28550263/article/details/134485123 目 录 1. 概述2. 垂直拖…

JDK1.5 新特性【泛型】

前言 泛型在 JavaSE 阶段是学习过的&#xff0c;但是毕竟处理定义一些简单的集合就很少用到它了&#xff0c;至于最近 Flink 中遇到的 泛型方法&#xff0c;更是感觉闻所未闻&#xff0c;以及源码中加在接口、方法、类前的各种 <T,V> 让我实在自觉羞愧&#xff0c;于是今…

leetcode34.排序数组中查找元素第一个和最后一个位置两种解题方法(超详细)

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/?envTypelist&envIdZCa7r67M这道题&#xff0c;读者可能会说这道题有什么好…

二阶低通滤波器(二阶巴特沃斯滤波器)

连续传递函数G(s) 离散传递函数G(z) 差分方程形式 二阶巴特沃斯滤波器参数设计 设计采样频率100Hz&#xff0c;截止频率33Hz。 注意&#xff1a;设计参数使用在离散系统中&#xff01; 同理&#xff0c;其他不同阶数不同类型的滤波器设计&#xff0c;如二阶高通滤波器、二阶…

Transformer ZOO

Natural Language Processing Transformer:Attention is all you need URL(46589)2017.6 提出Attention机制可以替代卷积框架。引入Position Encoding&#xff0c;用来为序列添加前后文关系。注意力机制中包含了全局信息自注意力机制在建模序列数据中的长期依赖关系方面表现出…

嵌入式开发--赛普拉斯cypress的铁电存储器FM25CL64B

嵌入式开发–赛普拉斯cypress的铁电存储器FM25CL64B 简介 FM25CL64B是赛普拉斯cypress出品的一款铁电存储器&#xff0c;这种存储器最大的优势是可以像RAM一样随机存储&#xff0c;和按字节写入&#xff0c;也可以像ROM一样掉电仍然可以保存数据&#xff0c;是一种相当优秀的…

宠物信息服务预约小程序的效果如何

宠物的作用越来越重要&#xff0c;因此铲屎官们对自己爱宠的照顾也是加倍提升&#xff0c;而市场围绕宠物展开的细分服务近些年来逐渐增多&#xff0c;且市场规模快速增长。涉及之广&#xff0c;涵盖宠物衣食住行、医疗、美容、婚丧嫁娶等&#xff0c;各品牌争相抢夺客户及抢占…

java游戏制作-拼图游戏

一.制作主界面 首先创建一个Java项目命名为puzzlegame。 再在src中创建一个包&#xff0c;用来制作主界面 代码&#xff1a; 结果&#xff1a; 二.设置界面 代码&#xff1a; 三.初始化界面 代码&#xff1a; 优化代码&#xff1a; 结果&#xff1a; 四.添加图片 先在Java项…