当前位置: 首页 > news >正文

浅谈前端设计模式:策略模式和状态模式的异同点

一、策略模式

策略模式是定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。 而且策略模式是重构小能力,特别适合拆分“胖逻辑”

这个定义乍一看会有点懵,不过通过下面的例子就能慢慢理解它的意思。

先来看一个真实场景

某次活动要做差异化询价。啥是差异化询价?就是说同一个商品,我通过在后台给它设置不同的价格类型,可以让它展示不同的价格。具体的逻辑如下:

  • 当价格类型为“预售价”时,满 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代表正吡啶基二硫化物或邻吡啶基二硫代,与硫醇、…...

开发微服务电商项目演示(四)

一&#xff0c;网关服务限流熔断降级第1步&#xff1a;启动sentinel-dashboard控制台和Nacos注册中心服务第2步&#xff1a;在网关服务中引入sentinel依赖<!-- sentinel --> <dependency><groupId>com.alibaba.cloud</groupId><artifactId>sprin…...

【C语言学习笔记】:静态库

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

社科院与杜兰大学中外合作办学金融管理硕士——30+的年龄在职读研有必要吗?

说起读研&#xff0c;年龄在什么区间最合适呢&#xff1f;上次有位咨询的同学反馈年龄已经快35岁了&#xff0c;有一份不错的工作&#xff0c;但又不甘心止步于此&#xff0c;想要通过提升学历升职加薪&#xff0c;但又纠结自己是否能静下心来学习、是否能顺利毕业、拿到的证书…...

2.13作业【设备树解析,按自己理解】

设备树定义 设备树&#xff08;device tree是描述硬件信息的一种树形结构&#xff0c;设备书文件在linux内核启动后被内核解析。描述一个硬件设备信息的节点我们叫做设备节点&#xff0c;一个设备节点内部包含当前硬件的多个不同属性&#xff0c;相同节点不同属性是以链式结构存…...

《NFL星计划》:巴尔的摩乌鸦·橄榄1号位

巴尔的摩乌鸦&#xff08;英语&#xff1a;Baltimore Ravens&#xff09;是一支职业美式橄榄球球队位于马里兰州的巴尔的摩。他们现时为美国美式橄榄球联合会的北区进行比赛&#xff0c;其主场为M&T银行体育场。乌鸦队曾在2000年和2012年取得超级碗冠军。 巴尔的摩乌鸦 成…...

Allegro如何设置自动保存和自动保存的时间操作指导

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

Kotlin实现简单音乐播放器

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

ShardingSphere-Proxy 数据库协议交互解读

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

基于ubuntu20.4的wine的MDK5软件的安装

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

Jmeter之直连数据库框架搭建简介

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

备战蓝桥杯【高精度乘法和高精度除法】

&#x1f339;作者:云小逸 &#x1f4dd;个人主页:云小逸的主页 &#x1f4dd;Github:云小逸的Github &#x1f91f;motto:要敢于一个人默默的面对自己&#xff0c;强大自己才是核心。不要等到什么都没有了&#xff0c;才下定决心去做。种一颗树&#xff0c;最好的时间是十年前…...

火眼审阅 | 基于NLP和OCR识别技术赋能合同审阅

合同作为确定权利义务的法律文件&#xff0c;贯穿企业内外部活动的所有环节&#xff0c;可见合同数据之于企业是非常重要的数据资产。 合同管理是企业营业中的重要部分&#xff0c;其中合同审核是企业法务的基本工作之一。而对于所有的法务人员一直存在一个问题&#xff1a;合…...

关于在集合中对象比较属性值的问题

关于在集合中对象比较属性值的问题1 问题说明2 问题排查3 总结及伪代码楼主在最近遇到一个场景&#xff0c;项目中有一个校验。 需要将数据库查询的集合对象与前端传递的集合对象进行比较&#xff0c;看数据是否被修改。 1 问题说明 基于上面项目需求&#xff0c;项目为较老的…...

java微信小程序旅游管理系统

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

2023年要跟踪的11个销售管理关键指标

销售管理关键指标有&#xff1a;营销合格线索数量&#xff08;MQL&#xff09;、MQL 到 SQL 的转换率、商机赢单率、获客成本、总销售额、客户终身价值&#xff08;LTV&#xff09;、LTV 与 CAC 比率、赢单周期、每客户平均销售额&#xff08;平均客单价&#xff09;、每销售人…...

MongoDB--》基本常用命令使用

目录 数据库操作命令 选择和创建数据库 数据库的删除 集合操作命令 集合的显示创建 集合的隐式创建 集合的删除 文档基本的CRUD&#xff08;增删改查&#xff09; 文档的插入 文档的基本查询 文档的更新 删除文档 数据库操作命令 数据库常用的操作命令如下&#x…...

js浮点数四则运算精度丢失以及toFixed()精度丢失解决方法

js浮点数四则运算精度丢失以及tofixed精度丢失解决方法一、js浮点数计算精度丢失的一些例子1、四则运算精度丢失&#xff1a;2、toFixed() 四舍五入精度丢失&#xff1a;二、浮点数计算精度丢失的原因三、解决办法1、使用 big.js&#xff08;如果有大量连续的计算推荐使用&…...

高姿态下的面部表情识别系统

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

English Learning - Day59 作业打卡 2023.2.13 周一

English Learning - Day59 作业打卡 2023.2.13 周一引言1. 我有一些急事要处理。2. 这个孩子无忧无虑。3. 那个骑在白马上的姑娘是我姐姐。4. 对方正在给我们公司施加压力迫使我们降价。5. 我的医生告诉我要少吃垃圾食品。6. 我从来不熬夜。7.我早就想跟你聊一聊了。8.我一定不…...

图机器学习

图机器学习1、图机器学习导论1.1图神经网络与普通神经网络的异同2、图的基本表示和特征工程2.1 图的基本表示2.1.1 图的本体设计2.1.2 图的种类2.1.3节点连接数&#xff08;度&#xff09;2.1.4图的基本表示&#xff08;邻接矩阵&#xff09;节点数量少使用2.1.5图的基本表示&a…...

ArcGIS中ArcMap创建渔网Create Fishnet:生成指定大小的格网矢量文件

本文介绍在ArcMap软件中&#xff0c;通过“Create Fishnet”工具创建渔网&#xff0c;从而获得指定大小的矢量格网数据的方法。 首先&#xff0c;我们在创建渔网前&#xff0c;需要指定渔网覆盖的范围。这里我们就以四川省为例&#xff0c;在这一范围内创建渔网&#xff1b;其中…...

TensorRT中的自定义层

TensorRT中的自定义层 文章目录TensorRT中的自定义层9.1. Adding Custom Layers Using The C API9.1.1. Example: Adding A Custom Layer With Dynamic Shape Support Using C重要提示&#xff1a;覆盖检查索引小于pos的连接的格式/类型&#xff0c;但绝不能检查索引大于pos的连…...

部署智能合约到公链

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 &#x1f3c5;阿里云ACE认证高级工程师 &#x1f3c5;阿里云开发者社区专…...

Windows server——部署DNS服务(3)

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.管理DNS服务 1.子域 案例 2. 委派 案例 1&#xff09;添加主机记录 …...

9. QML_OpenGL--2. 在QQuick中搭建加载OpenGL框架

1. 说明&#xff1a; OPenGL一般在 QtWidget 中使用&#xff0c;但目前使用 QML 做界面开发是一种趋势&#xff0c;同时在QML中使用OPenGL进行渲染也是十分必要&#xff0c;文章简单介绍如何在QML中使用 OPenGL&#xff0c;搭建了一种基本的框架。整体思路和在 QtWidget 中类似…...

亚马逊云科技携手滴普科技,打造数据智能新标杆

随着企业数字化转型的不断深入&#xff0c;数据对于业务的价值和重要性也逐渐凸显。越来越多企业意识到&#xff0c;只有不断提升底层数据基础平台的性能和能力&#xff0c;才能构建数据驱动的业务&#xff0c;增强企业核心竞争力。作为湖仓一体数据智能基础软件独角兽企业&…...

CGO 跨平台静态编译

什么是跨平台编译&#xff1f; 跨平台编译&#xff1a;即交叉编译&#xff0c;是在一个平台上生成另一个平台上的可执行文件。所谓平台&#xff0c;实际上包含两个概念&#xff1a;体系架构(Architecture)、操作系统 (Operating System&#xff09;。同一个体系架构可以运行不同…...

股票买卖接口怎么来的?

现在股票买卖接口主要是在线上研发&#xff0c;有专业的开发团队进行源码开发和完善&#xff0c;但是&#xff0c;常常会在开发过程中出现问题&#xff0c;也就是遇到一些特殊的情况需要及时处理&#xff0c;那么股票买卖接口怎么开发实现出来的&#xff1f;一、股票买卖接口开…...

国内网站建设联系电话/西安危机公关公司

1 我们的经验是长度公理: 对直线上的一些点集构成的集族, 指定其上的一个函数 $m$, 使得 (1) 非负性 (non-negativity): $mE\geq 0$; (2) 有限可加性 (finitely additivity): 若 $\sed{E_i}_{i1}^j$ 互不相交, 则 $$\bex m(E_1\cup \cdots\cup E_j)mE_1\cdotsmE_j; \ee…...

惠州的企业网站建设/上首页的seo关键词优化

最近写代码&#xff0c;需要停止interval之后再重新启动&#xff0c;开始使用代码如下&#xff0c;发现无法重新启动 function func(){console.log("print")} //定时任务 var interval setInterval(func, 2000); //启动,func不能使用括号 clearInterval(interval );…...

做儿童业态招商要去哪些网站/推广一般去哪发帖

SpringBoot使用注解方式开启定时任务 1&#xff09;启动类里面 EnableScheduling开启定时任务&#xff0c;自动扫描 2&#xff09;定时任务业务类 加注解 Component被容器扫描 3&#xff09;定时执行的方法加上注解 Scheduled(fixedRate20…...

做网站别名解析的目的是什么/西藏自治区seo 标题 关键词优化

作者介绍Scott Meyers&#xff0c;C顶级权威之一&#xff0c;为世界各地客户提供培训和咨询服务。出版有畅销的Effective C系列图书&#xff08;《Effective C》、《More Effective C》和《Effective STL》&#xff09;&#xff0c;设计了创新型的Effective C CD&#xff0c;Ad…...

淘客网站开发/网站建设怎么弄

Go语言动手写Web框架——前缀树路由什么是前缀树前缀树的结点参考链接: 极客兔兔主页.什么是前缀树 在进行web框架设计时&#xff0c;我们可以选用一个表用于记载路由地址与对应的Handler&#xff0c;但是这存在着一个问题&#xff0c;当遇到动态路由时&#xff08;例如&…...

网站平台怎么做的/seo综合查询工具可以查看哪些数据

随着IPv4地址即将用尽&#xff0c;全球已经加速进入IPv6时代&#xff0c;目前电脑中也可以查看到IPv6地址信息&#xff0c;不过很多朋友对于IPv6是什么不太了解&#xff0c;下面通过ipv6地址知识百科以及怎么查看电脑IPv6地址&#xff0c;让大家深入了解一下。ipv6是什么?专业…...