浅谈前端设计模式:策略模式和状态模式的异同点
一、策略模式
策略模式是定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。 而且策略模式是重构小能力,特别适合拆分“胖逻辑”。
这个定义乍一看会有点懵,不过通过下面的例子就能慢慢理解它的意思。
先来看一个真实场景
某次活动要做差异化询价。啥是差异化询价?就是说同一个商品,我通过在后台给它设置不同的价格类型,可以让它展示不同的价格。具体的逻辑如下:
- 当价格类型为“预售价”时,满 100 - 20,不满 100 打 9 折
- 当价格类型为“大促价”时,满 100 - 30,不满 100 打 8 折
- 当价格类型为“返场价”时,满 200 - 50,不叠加
- 当价格类型为“尝鲜价”时,直接打 5 折
首先将四种价格做了标签化:
预售价 - pre
大促价 - onSale
返场价 - back
尝鲜价 - fresh
大部分人可能会这么写
// 询价方法,接受价格标签和原价为入参
function askPrice(tag, originPrice) {// 处理预热价if(tag === 'pre') {if(originPrice >= 100) {return originPrice - 20} return originPrice * 0.9}// 处理大促价if(tag === 'onSale') {if(originPrice >= 100) {return originPrice - 30} return originPrice * 0.8}// 处理返场价if(tag === 'back') {if(originPrice >= 200) {return originPrice - 50}return originPrice}// 处理尝鲜价if(tag === 'fresh') { return originPrice * 0.5}
}
上述代码运行起来确实没啥毛病。但也只是“运行起来”没毛病而已。
问题点:
- 首先,它违背了“单一功能”原则。一个函数里面竟然处理了四个逻辑,这个函数的逻辑太胖了!而且单个能力很难被抽离复用。
- 另外,它还违背了“开放封闭”原则。假如再加一个满 100 - 50 的“新人价”就只能在这个函数中修改代码,继续加if逻辑。
重构询价逻辑
现在我们基于设计原则思想(“单一功能”和开放封闭”原则),一点一点改造掉这个臃肿的 askPrice。
单一功能改造
首先,我们赶紧把四种询价逻辑提出来,让它们各自为政:
// 处理预热价
function prePrice(originPrice) {if(originPrice >= 100) {return originPrice - 20} return originPrice * 0.9
}// 处理大促价
function onSalePrice(originPrice) {if(originPrice >= 100) {return originPrice - 30} return originPrice * 0.8
}// 处理返场价
function backPrice(originPrice) {if(originPrice >= 200) {return originPrice - 50}return originPrice
}// 处理尝鲜价
function freshPrice(originPrice) {return originPrice * 0.5
}function askPrice(tag, originPrice) {// 处理预热价if(tag === 'pre') {return prePrice(originPrice)}// 处理大促价if(tag === 'onSale') {return onSalePrice(originPrice)}// 处理返场价if(tag === 'back') {return backPrice(originPrice)}// 处理尝鲜价if(tag === 'fresh') { return freshPrice(originPrice)}
}
OK,我们现在至少做到了一个函数只做一件事。现在每个函数都有了自己明确的、单一的分工。
到这里,在单一功能原则的指引下,我们已经解决了一半的问题。我们已经把“询价逻辑的执行”给摘了出去,并且实现了不同询价逻辑之间的解耦。
开放封闭改造
如果现在要想给 askPrice 增加新人询价逻辑,应该怎么办? 如果在 askPrice 里面,新增了一个 if-else 判断。这样其实还是在修改 askPrice 的函数体,没有实现“对扩展开放,对修改封闭”的效果。
那么我们应该怎么做?我们仔细想想,这么多 if-else,我们的目的到底是什么?是不是就是为了把 询价标签-询价函数 这个映射关系给明确下来?那么在 JS 中,有没有什么既能够既帮我们明确映射关系,同时不破坏代码的灵活性的方法呢?答案就是对象映射!
咱们完全可以把询价算法全都收敛到一个对象里去:
// 定义一个询价处理器对象
const priceProcessor = {pre(originPrice) {if (originPrice >= 100) {return originPrice - 20;}return originPrice * 0.9;},onSale(originPrice) {if (originPrice >= 100) {return originPrice - 30;}return originPrice * 0.8;},back(originPrice) {if (originPrice >= 200) {return originPrice - 50;}return originPrice;},fresh(originPrice) {return originPrice * 0.5;},
};
当我们想使用其中某个询价算法的时候:通过标签名去定位就好了:
// 询价函数
function askPrice(tag, originPrice) {return priceProcessor[tag](originPrice)
}
如此一来,askPrice 函数里的 if-else 大军彻底被咱们消灭了。这时候如果你需要一个新人价,只需要给 priceProcessor 新增一个映射关系即可:
priceProcessor.newUser = function (originPrice) {if (originPrice >= 100) {return originPrice - 50;}return originPrice;
}
这样一来,询价逻辑的分发也变成了一个清清爽爽的过程。二、状态模式
状态模式(State Pattern) :允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中,可以把复杂的判断逻辑简化。
状态模式和策略模式宛如一对孪生兄弟——它们长得很像、解决的问题也可以说没啥本质上的差别。
以咖啡机为例讲状态模式
在这个能做四种咖啡的咖啡机体内,蕴含着四种状态:
- 美式咖啡态(american):只吐黑咖啡
- 普通拿铁态(latte):黑咖啡加点奶
- 香草拿铁态(vanillaLatte):黑咖啡加点奶再加香草糖浆
- 摩卡咖啡态(mocha):黑咖啡加点奶再加点巧克力
大部分人第一反应还是会先用if-else完成如下
class CoffeeMaker {constructor() {/**这里略去咖啡机中与咖啡状态切换无关的一些初始化逻辑**/// 初始化状态,没有切换任何咖啡模式this.state = 'init';}// 关注咖啡机状态切换函数changeState(state) {// 记录当前状态this.state = state;if(state === 'american') {// 这里用 console 代指咖啡制作流程的业务逻辑console.log('我只吐黑咖啡');} else if(state === 'latte') {console.log(`给黑咖啡加点奶`);} else if(state === 'vanillaLatte') {console.log('黑咖啡加点奶再加香草糖浆');} else if(state === 'mocha') {console.log('黑咖啡加点奶再加点巧克力');}}
}
测试一下,完美无缺:
const mk = new CoffeeMaker();
mk.changeState('latte'); // 输出 '给黑咖啡加点奶'
但是考虑到设计原则中的“单一职责、开放封闭”原则,我们还需要如同上面策略模式中的改造方法进行对上面代码的改造。
根据“单一职责、开放封闭”原则改造后
const stateToProcessor = {american() {console.log('我只吐黑咖啡');},latte() {this.american();console.log('加点奶');},vanillaLatte() {this.latte();console.log('再加香草糖浆');},mocha() {this.latte();console.log('再加巧克力');}
}class CoffeeMaker {constructor() {/**这里略去咖啡机中与咖啡状态切换无关的一些初始化逻辑**/// 初始化状态,没有切换任何咖啡模式this.state = 'init';}// 关注咖啡机状态切换函数changeState(state) {// 记录当前状态this.state = state;// 若状态不存在,则返回if(!stateToProcessor[state]) {return ;}stateToProcessor[state]();}
}const mk = new CoffeeMaker();
mk.changeState('latte');
输出结果符合预期:
我只吐黑咖啡
加点奶
现在看起来好像很完善了,但是stateToProcessor 里的工序函数,感知不到咖啡机的内部状况。
为了让stateToProcessor里的工序函数能感知到咖啡机的内部状况,把状态-行为映射对象作为主体类对应实例的一个属性添加进去就行了:
class CoffeeMaker {constructor() {/**这里略去咖啡机中与咖啡状态切换无关的一些初始化逻辑**/// 初始化状态,没有切换任何咖啡模式this.state = 'init';// 初始化牛奶的存储量this.leftMilk = '500ml';}stateToProcessor = {that: this,american() {// 尝试在行为函数里拿到咖啡机实例的信息并输出console.log('咖啡机现在的牛奶存储量是:', this.that.leftMilk)console.log('我只吐黑咖啡');},latte() {this.american()console.log('加点奶');},vanillaLatte() {this.latte();console.log('再加香草糖浆');},mocha() {this.latte();console.log('再加巧克力');}}// 关注咖啡机状态切换函数changeState(state) {this.state = state;if (!this.stateToProcessor[state]) {return;}this.stateToProcessor[state]();}
}const mk = new CoffeeMaker();
mk.changeState('latte');
输出结果为:
咖啡机现在的牛奶存储量是: 500ml
我只吐黑咖啡
加点奶
如此一来,我们就可以在 stateToProcessor 轻松拿到咖啡机的实例对象,进而感知咖啡机这个主体了。
策略模式和状态模式的异同点
策略模式和状态模式确实是相似的,它们都封装行为、都通过委托来实现行为分发。但策略模式中的行为函数是“潇洒”的行为函数(策略模式是对算法的封装,封装的算法可以单独使用),它们不依赖调用主体、互相平行、各自为政,井水不犯河水。而状态模式中的行为函数,首先是和状态主体之间存在着关联,由状态主体把它们串在一起;另一方面,正因为关联着同样的一个(或一类)主体,所以不同状态对应的行为函数可能并不会特别割裂。
前端中的状态模式应用——javascript-state-machine
最后
整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。
有需要的小伙伴,可以点击下方卡片领取,无偿分享
相关文章:

浅谈前端设计模式:策略模式和状态模式的异同点
一、策略模式 策略模式是定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。 而且策略模式是重构小能力,特别适合拆分“胖逻辑”。 这个定义乍一看会有点懵,不过通过下面的例子就能慢慢理解它的意思。 先来看一个真实场景 某次活动要做…...

线性杂双功能PEG试剂OPSS-PEG-Acid,OPSS-PEG-COOH,巯基吡啶聚乙二醇羧基
英文名称:OPSS-PEG-COOH,OPSS-PEG-Acid 中文名称:巯基吡啶-聚乙二醇-羧基 OPSS-PEG-COOH是一种具有OPSS和羧基的线性杂双功能PEG试剂。它是一种有用的带有PEG间隔基的交联剂。OPSS代表正吡啶基二硫化物或邻吡啶基二硫代,与硫醇、…...

开发微服务电商项目演示(四)
一,网关服务限流熔断降级第1步:启动sentinel-dashboard控制台和Nacos注册中心服务第2步:在网关服务中引入sentinel依赖<!-- sentinel --> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>sprin…...

【C语言学习笔记】:静态库
一、什么是库 库是写好的现有的,成熟的,可以复用的代码。现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。 本质上来说库是一种可执行代码的二进制形式,可以被操作…...

社科院与杜兰大学中外合作办学金融管理硕士——30+的年龄在职读研有必要吗?
说起读研,年龄在什么区间最合适呢?上次有位咨询的同学反馈年龄已经快35岁了,有一份不错的工作,但又不甘心止步于此,想要通过提升学历升职加薪,但又纠结自己是否能静下心来学习、是否能顺利毕业、拿到的证书…...

2.13作业【设备树解析,按自己理解】
设备树定义 设备树(device tree是描述硬件信息的一种树形结构,设备书文件在linux内核启动后被内核解析。描述一个硬件设备信息的节点我们叫做设备节点,一个设备节点内部包含当前硬件的多个不同属性,相同节点不同属性是以链式结构存…...
《NFL星计划》:巴尔的摩乌鸦·橄榄1号位
巴尔的摩乌鸦(英语:Baltimore Ravens)是一支职业美式橄榄球球队位于马里兰州的巴尔的摩。他们现时为美国美式橄榄球联合会的北区进行比赛,其主场为M&T银行体育场。乌鸦队曾在2000年和2012年取得超级碗冠军。 巴尔的摩乌鸦 成…...

Allegro如何设置自动保存和自动保存的时间操作指导
Allegro如何设置自动保存和自动保存的时间操作指导 做PCB设计的时候,自动保存软件是一个必要的功能,Allegro同样支持设置自动保存,而且可以设置自动保存的时间。 如下图 具体操作如下 点击Setup点击User Preferences...

Kotlin实现简单音乐播放器
关于音乐播放器,我真的是接触比较多,听歌作为我第一大爱好,之前也用Java设计过音乐播放器,感兴趣的同学可以阅读:Android Studio如何实现音乐播放器(简单易上手)和 Android Studio实现音乐播放器…...

ShardingSphere-Proxy 数据库协议交互解读
数据库协议对于大部分开发者来说算是比较冷门的知识,一般的用户、开发者都是通过现成的数据库客户端、驱动使用数据库,不会直接操作数据库协议。不过,对数据库协议的特点与流程有一些基本的了解,有助于开发者在排查数据库功能、性…...

基于ubuntu20.4的wine的MDK5软件的安装
本文基于ubuntu20.4安装MDK5的keil软件,由于MDK不提供linux版本的安装软件,因此需要利用wine软件来安装MDK5软件,具体流程包括wine软件安装、MDK5安装及MDK5的lic添加等3部分内容。具体流程如下所示: (一)…...

Jmeter之直连数据库框架搭建简介
案例简介 通过直连数据库让程序代替接口访问数据库,如果二者预期结果不一致,就找到了程序的缺陷。 下面通过一个案例分析讲解如何实现:获取某个字段值,放在百度上搜索。 实现方式 1、Jmeter本身不具备直连数据库的功能…...

备战蓝桥杯【高精度乘法和高精度除法】
🌹作者:云小逸 📝个人主页:云小逸的主页 📝Github:云小逸的Github 🤟motto:要敢于一个人默默的面对自己,强大自己才是核心。不要等到什么都没有了,才下定决心去做。种一颗树,最好的时间是十年前…...

火眼审阅 | 基于NLP和OCR识别技术赋能合同审阅
合同作为确定权利义务的法律文件,贯穿企业内外部活动的所有环节,可见合同数据之于企业是非常重要的数据资产。 合同管理是企业营业中的重要部分,其中合同审核是企业法务的基本工作之一。而对于所有的法务人员一直存在一个问题:合…...
关于在集合中对象比较属性值的问题
关于在集合中对象比较属性值的问题1 问题说明2 问题排查3 总结及伪代码楼主在最近遇到一个场景,项目中有一个校验。 需要将数据库查询的集合对象与前端传递的集合对象进行比较,看数据是否被修改。 1 问题说明 基于上面项目需求,项目为较老的…...

java微信小程序旅游管理系统
本旅游服务软件,主要实现了管理员后端:首页、个人中心、旅游攻略管理、旅游资讯管理、景点信息管理、门票预定管理、用户管理、酒店信息管理、酒店预定管理、推荐路线管理、论坛管理、系统管理,用户前端:首页、景点信息、酒店信息、论坛中心、我的等。总…...

2023年要跟踪的11个销售管理关键指标
销售管理关键指标有:营销合格线索数量(MQL)、MQL 到 SQL 的转换率、商机赢单率、获客成本、总销售额、客户终身价值(LTV)、LTV 与 CAC 比率、赢单周期、每客户平均销售额(平均客单价)、每销售人…...

MongoDB--》基本常用命令使用
目录 数据库操作命令 选择和创建数据库 数据库的删除 集合操作命令 集合的显示创建 集合的隐式创建 集合的删除 文档基本的CRUD(增删改查) 文档的插入 文档的基本查询 文档的更新 删除文档 数据库操作命令 数据库常用的操作命令如下&#x…...
js浮点数四则运算精度丢失以及toFixed()精度丢失解决方法
js浮点数四则运算精度丢失以及tofixed精度丢失解决方法一、js浮点数计算精度丢失的一些例子1、四则运算精度丢失:2、toFixed() 四舍五入精度丢失:二、浮点数计算精度丢失的原因三、解决办法1、使用 big.js(如果有大量连续的计算推荐使用&…...

高姿态下的面部表情识别系统
效果展示: python表情、性别识别面部表情识别 (FER) 在计算机安全、神经科学、心理学和工程学方面有大量应用。由于其非侵入性,它被认为是打击犯罪的有用技术。然而,FER 面临着几个挑战,其中最严重的是它在严重的头部姿势下的预测…...

IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...

超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

【力扣数据库知识手册笔记】索引
索引 索引的优缺点 优点1. 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度(创建索引的主要原因)。3. 可以加速表和表之间的连接,实现数据的参考完整性。4. 可以在查询过程中,…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
es6+和css3新增的特性有哪些
一:ECMAScript 新特性(ES6) ES6 (2015) - 革命性更新 1,记住的方法,从一个方法里面用到了哪些技术 1,let /const块级作用域声明2,**默认参数**:函数参数可以设置默认值。3&#x…...

uni-app学习笔记三十五--扩展组件的安装和使用
由于内置组件不能满足日常开发需要,uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件,需要安装才能使用。 一、安装扩展插件 安装方法: 1.访问uniapp官方文档组件部分:组件使用的入门教程 | uni-app官网 点击左侧…...