Javascript 职责链模式

news2024/9/22 23:31:57

现实中的职责链模式

职责链模式的例子在现实中并不难找到,以下就是两个常见的跟职责链模式有关的场景。

❏ 如果早高峰能顺利挤上公交车的话,那么估计这一天都会过得很开心。因为公交车上人实在太多了,经常上车后却找不到售票员在哪,所以只好把两块钱硬币往前面递。除非你运气够好,站在你前面的第一个人就是售票员,否则,你的硬币通常要在N个人手上传递,才能最终到达售票员的手里。

❏ 如果早高峰能顺利挤上公交车的话,那么估计这一天都会过得很开心。因为公交车上人实在太多了,经常上车后却找不到售票员在哪,所以只好把两块钱硬币往前面递。除非你运气够好,站在你前面的第一个人就是售票员,否则,你的硬币通常要在N个人手上传递,才能最终到达售票员的手里。

以订单优惠券为例:

假设我们负责一个售卖手机的电商网站,经过分别交纳500元定金和200元定金的两轮预定后(订单已在此时生成),现在已经到了正式购买的阶段。

公司针对支付过定金的用户有一定的优惠政策。在正式购买后,已经支付过500元定金的用户会收到100元的商城优惠券,200元定金的用户可以收到50元的优惠券,而之前没有支付定金的用户只能进入普通购买模式,也就是没有优惠券,且在库存有限的情况下不一定保证能买到。

我们的订单页面是PHP吐出的模板,在页面加载之初,PHP会传递给页面几个字段。
❏ orderType:表示订单类型(定金用户或者普通购买用户), code的值为1的时候是500元定金用户,为2的时候是200元定金用户,为3的时候是普通购买用户。

❏ pay:表示用户是否已经支付定金,值为true或者false,虽然用户已经下过500元定金的订单,但如果他一直没有支付定金,现在只能降级进入普通购买模式。

❏ stock:表示当前用于普通购买的手机库存数量,已经支付过500元或者200元定金的用户不受此限制。

不用职责模式

你用职责链模式,你可以会这么写:

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

            else if ( orderType === 2 ){     // 200元定金购买模式
              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种购买模式的节点函数,我们约定,如果某个节点不能处理请求,则返回一个特定的字符串’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,表示在链中的下一个节点。此外Chain的prototype中还有两个函数,它们的作用如下所示:

        // Chain.prototype.setNextSuccessor  指定在链中的下一个节点
        // Chain.prototype.passRequest  传递请求给某个节点

        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 );     // 输出:手机库存不足

通过改进,我们可以自由灵活地增加、移除和修改链中的节点顺序,假如某天网站运营人员又想出了支持300元定金购买,那我们就在该链中增加一个节点即可:

        var order300 = function(){
            // 具体实现略
        };

        chainOrder300= new Chain( order300 );
        chainOrder500.setNextSuccessor( chainOrder300);
        chainOrder300.setNextSuccessor( chainOrder200);

异步的职责链

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

这时候让节点函数同步返回"nextSuccessor"已经没有意义了,所以要给Chain类再增加一个原型方法Chain.prototype.next,表示手动传递请求给职责链中的下一个节点:

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

现在我们得到了一个特殊的链条,请求在链中的节点里传递,但节点有权利决定什么时候把请求交给下一个节点。可以想象,异步的职责链加上命令模式(把ajax请求封装成命令对象,详情请参考第9章),我们可以很方便地创建一个异步ajax队列库。

职责链模式的优缺点

前面已经说过,职责链模式的最大优点就是解耦了请求发送者和N个接收者之间的复杂关系,由于不知道链中的哪个节点可以处理你发出的请求,所以你只需把请求传递给第一个节点即可,
在这里插入图片描述
在这里插入图片描述

优点

用职责链模式改进后:在手机商城的例子中,本来我们要被迫维护一个充斥着条件分支语句的巨大的函数,在例子里的购买过程中只打印了一条log语句。其实在现实开发中,这里要做更多事情,比如根据订单种类弹出不同的浮层提示、渲染不同的UI节点、组合不同的参数发送给不同的cgi等。用了职责链模式之后,每种订单都有各自的处理函数而互不影响。

缺点

职责链模式使得程序中多了一些节点对象,可能在某一次的请求传递过程中,大部分节点并没有起到实质性的作用,它们的作用仅仅是让请求传递下去,从性能方面考虑,我们要避免过长的职责链带来的性能损耗。

用AOP实现职责链

unction.prototype.after函数,使得第一个函数返回’nextSuccessor’时,将请求继续传递给下一个函数,无论是返回字符串’nextSuccessor’或者false都只是一个约定,当然在这里我们也可以让函数返回false表示传递请求,选择’nextSuccessor’字符串是因为它看起来更能表达我们的目的。

        Function.prototype.after = function( fn ){
            var self = this;
            return function(){
              var ret = self.apply( this, arguments );
              if ( ret === 'nextSuccessor' ){
                  return fn.apply( this, arguments );
              }

              return ret;
            }
        };

        var order = order500yuan.after( order200yuan ).after( orderNormal );

        order( 1, true, 500 );    // 输出:500元定金预购,得到100优惠券
        order( 2, true, 500 );    // 输出:200元定金预购,得到50优惠券
        order( 1, false, 500 );   // 输出:普通购买,无优惠券

用AOP来实现职责链既简单又巧妙,但这种把函数叠在一起的方式,同时也叠加了函数的作用域,如果链条太长的话,也会对性能有较大的影响。

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

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

相关文章

年前最后一次分享5款小工具

马上要回家过年了,今年最后一次分享,希望大家喜欢。 1.图片管理器——Imagine 在管理器支持直接预览压缩包图片。支持图片编辑、图片批量转换、批量重命名、支持 GIF 动态图片编辑。如插入帧、修改帧的速度、循环播放、尺寸。同时还支持让系统右键菜单…

熟悉 NestJS (文末附视频)

前言 经过了需求分析以及技术选型之后,我们正式步入了第三个环节:脚手架搭建。 工欲善其事,必先利其器,NestJS 为开发者提供了很多开箱即用的功能,我们可以根据团队的需求搭建一套适配所有业务开发的基础脚手架。所以…

帮助有一定计算机基础的人 快速复习并重新拾起C语言基础

这里写目录标题1.C语言程序举例2.详解C语言程序结构1)#include2)main 函数:3){} 括号,程序体和代码块4)注释5)print 函数6)return 语句3 C程序的编译步骤是怎样?4.数据的…

【Go基础】面向对象和反射机制

文章目录一、面向对象1. 面向对象的概念2. 构造函数3. 继承与重写4. 泛型二、反射1. 反射介绍2. 反射的基础数据类型3. 反射API3.1 reflect.Type①如何得到Type②指针Type转为非指针Type③获取struct成员变量的信息④获取struct成员方法的信息⑤获取函数的信息⑥判断类型是否实…

『 MySQL篇 』:MySQL表的CURD操作

📢 MySQL 系列专栏持续更新中 … MySQL专栏 ​ 目录 目录一、SQL语句- SQL通用语法- 注释- SQL语句分类二、 基础表操作- 创建表- 查看库中的表- 查看表结构- 删除表- 重命名表三、MySQL 中的增删查改操作- 增加(insert语句)- 查询(select语…

CSS 使用 @font-face 引入外部字体

CSS 使用 font-face 引入外部字体下载所需字体到本地把下载字体文件放入font文件夹里定义字体引用字体结果😬没有退路时,潜能就发挥出来了 CSS 中使用开源字体 得意黑 得意黑的字体是真的好看 ✨推荐使用 下载所需字体到本地 这里介绍一款不错的中文字…

Shiro:核心组件、配置类、多Realm场景、自定义拦截器、实战场景

目录Shiro 的核心组件Shiro 认证流程Shiro 授权流程单 RealmShiro 登陆认证 SimpleAuthenticationInfo 对象多 RealmShiroConfigShiro过滤器配置 ShiroFilterFactoryBeanShiro自定义过滤器Shiro 过滤器执行链路梳理代码自取层级结构Login.javaBearerTokenRealm.javaShiroRealm.…

桶排序详细说明及实现-python

前言: 说到桶排序,那必定要有桶,那么桶的作用是什么呢?桶的作用就是将序列分为若干份放到桶中,每个桶中能装入的数量范围是一定的,只有最后一个桶可以设置装入很多。这是因为当分的桶一定时,前面…

SpringMVC-基础入门

文章目录SpringMVC1,SpringMVC概述2,SpringMVC入门案例2.1 需求分析2.2 案例制作步骤1:创建Maven项目步骤2:补全目录结构步骤3:导入jar包步骤4:创建配置类步骤5:创建Controller类步骤6:使用配置类替换web.xml步骤7:配置Tomcat环境步骤8:启动运行项目步骤…

【软考】系统集成项目管理工程师(十五)项目采购管理

一、项目采购管理概述二、项目采购管理子过程1. 编制采购管理计划2. 实施采购3. 控制采购4. 结束采购三、招投标1. 招标人的权利和义务2. 招标代理机构的权利和义务3. 招标方式和招投标程序4. 相关的法律责任一、项目采购管理概述 采购意味着从外界来源获得商品或服务,采购一…

Vue3组件初始化流程分析

本文主要来分析 vue3 组件的初始化(基于runtime-core(核心运行时)包),将从createApp、mount 等常用 API 入手来分析组件的挂载、普通元素的挂载流程。 createApp 1、创建一个应用实例。使用方式如下: import { createApp } from vue import App from ./App.vueco…

进制转换(二进制、八进制、十进制、十六进制)超详细版

今天来总结一下各种进制转换问题,详细齐全易于理解,希望对你有帮助哦! 各种进制之间的相互转换 先从我们最熟悉的十进制入手吧,其他进制与十进制的转换方法都是一样的。 整型有4种进制形式: 1.十进制: …

[ChatGPT]

最近hatGPT火爆全宇宙,几乎所有圈内人都在谈论这个美国人工智能公司OpenAI发布免费机器人对话模型ChatGPT(GPT-3.5系列),模型中首次采用RLHF(从人类反馈中强化学习)方式。模型目前处于测试阶段,…

Windows 服务器刷题(2)(带答案)

作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​​ 目录 前言 一.刷题 前言 本章将会讲解Windows服务器刷题(2) 一…

新年礼物已收到!2022 Apache IoTDB Commits 数量排名 3/351!

社区喜报!据 The Apache Software Foundation 官方 Projects Statistics(项目信息统计网站)的实时数据显示,Apache IoTDB 在过去 12 个月(即 2022 年度)共发表 6829 Commits,排名 2022 年度 Apa…

C++ Primer笔记——默认移动操作、移动迭代器、左右值引用成员函数、标准库仿函数、function包装器

目录 一.P476 合成的移动操作 二.P480 移动迭代器 三.P483 右值和左值引用成员函数 四.P510 标准库定义的仿函数 五.P512 标准库function类型(包装器) 一.P476 合成的移动操作 什么时候会有默认的移动构造和移动赋值函数,需满足以下几点…

Java设计模式-中介者模式Mediator

介绍 中介者模式(Mediator Pattern),用一个中介对象来封装一系列的对象交互。中介者使各个对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式属于行为型模式,使代…

linux搭建webapp实战

首先介绍下linux,linux因其开源,定制化高,安全等原因,成为了目前web应用部署首选的操作系统,linux操作系统有很多版本,常见的有centos,debian,RHLE,redhat,乌…

【Linux】gcc/g++编译器、make/Makefile自动化构建工具

作者:小卢 专栏:《Linux》 喜欢的话:世间因为少年的挺身而出,而更加瑰丽。 ——《人民日报》 目录 1.gcc/c的概念: 2.程序编译过程详解: 2.1程序编译过程: 2.…

微电网(风、光、储能、需求响应)【Simulink 仿真实现】

目录 1 展现 2 典型几个介绍 2.1 采用PR的三相逆变器电压控制 2.2 太阳能直流微电网系统 2.3 主电网故障时的交流微电网性能 2.4 混合光伏、双馈发电和电池能源的微电网集成 3 写在后面 4 完整资源 1 展现 随便打开一个,就以第一个(采用PID的三…