js设计模式(七)-行为型模式(备忘录模式/解释器模式/职责链模式)
备忘录模式
备忘录模式:在不破坏对象的封装性的前提下,在对象之外捕获并保存该对象内部的状态以便日后对象使用或者对象恢复到以前的某个状态。
这个模式其实蛮有意思的,说一个经常遇到但是很苦恼的场景:
一个创建流程分为3-5步,每一步都是新的弹窗,而且不是嵌套的弹窗。
会涉及到的操作:上一步、下一步、取消(关闭)、点击右上角中断操作(误操作)、分步提交。
麻烦点在哪呢?
- 点击下一步的时候,当前步骤的表单是否清空,清空的话下一步返回的话就需要赋值回来,下拉菜单有可能需要重新发起请求
- 点击上一步的时候,当前步骤的表单是否清空,如果不清空,可能当前步骤的一些选项和第一步有关联,清空的话,如果填写项目比较多,用户体验就很差
- 如果上述的都不清空,点击取消的时候,所有的表单都要清空...
- 如果弹框关闭要重置表单,误操作点了 【X】 把弹框关掉了,再打开数据都丢了...
- 分步提交时,每次提交结束以后后台返回的数据可能被下一步使用,但是点击上一步时候那些数据放在哪里,如何复用...
真的头大~~~
如果传统的开发方式,很容易想到会有很多个变量来存储这些值:
- 弹框展示状态
- 每一步的数据
- 每个表单的数据
- 每个下拉菜单的数据
- 每个按钮的方法
- ...
所以,备忘录模式最好用的地方其实就是解决这类问题:
class StepFlow {
constructor(max, baseData) {
this.max = max || 1
this.step = 1
this.baseData = baseData
this.state = {}
}
reset() {
this.step = 1
this.state = Object.assign({}, this.baseData)
}
setState(k, v) {
this.state[k] = v
console.log('setState', k, '=', v)
}
getState(k, filters) {
if (filters) {
return filters(this.state[k])
} else {
return this.state[k]
}
}
nextStep() {
this.step = this.step + 1 > this.max ? this.max : this.step + 1
console.log('now step is:', this.step)
}
lastStep() {
this.step = this.step - 1 < 1 ? 1 : this.step - 1
console.log('now step is:', this.step)
}
}
const flow = new StepFlow(3)
flow.nextStep()
flow.nextStep()
flow.nextStep()
flow.lastStep()
flow.lastStep()
flow.lastStep()
flow.setState('name', 'Luis')
flow.setState('age', 18)
flow.setState('sex', 1)
console.log('直接获取', flow.getState('sex'))
console.log(
'获取并过滤',
flow.getState('sex', (v) => (v === 1 ? '男' : '女'))
)
如上,初始化一个工作流的对象,并且将所有关键节点的数据都存进去,在流程结束以后销毁所有的数据。并且可以选择重置流程。
解释器模式
解释器模式:给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。
大家有没有遇到这个需求呀?
在一个 输入框中用户输入 11+1*(2+3)
,然后返回结果呢?当然这是实例化以后的解释器模式,但是确实是实际上的解释器模式,其实是一种抽象话的设计理念。
先说上面这个需求要怎么做吧,最简单的思路:
1. 首先先把字符串中的空格过滤掉
2. 然后从左网往右开始读取
1. 1 => [1] 数字直接push
2. 1 => [11] 数字,和上一个比对,也是数字,拼成新数字
3. + => [11, '+'] 符号,新建一项
4. 1 => [11, '+', 1] 上一项是符号,重新开始走数字逻辑
5. * => [11, '+', [1, '*']] 乘号,最近的数字进行组合,连乘的话,因为交换律,所以可以先算后边的,除法则相反
6. ( => [11, '+', [1, '*', []]] 左括号,再提升优先级
7. 2 => [11, '+', [1, '*', [2]]] 数字
8. + => [11, '+', [1, '*', [2, '+']]] 加号
9. 3 => [11, '+', [1, '*', [2, '+', 3]]] 数字
10. ) => 右括号,外面的降低优先级
3. 读取结束,开始从里往外计算:
1. [2, '+', 3] => 5
2. [1, '*' 5] => 5
3. [11, '+', 5] => 16
而实际中的使用则是一系列的方法,都在操作同一个数据,并且又先后调用关系,上一步的结果会被导入下一步中,是不是想到了链式调用,每次都返回一个对象?
这样耦合度就有点高了,所以就提出了这个设计模式,每个方法各自独立,由一个类似中介者的设计去依次触发那些方法。
写到这里,感觉解释器模式就是中介者模式的变形,他只有租客和中介者这半个,有没有很像是只把租客那些天马行空的想法转换成实际的房源信息上?
代码可以参考大佬们的底部有链接。
职责链模式
职责链模式:类似多米诺骨牌, 通过请求第一个条件, 会持续执行后续的条件, 直到返回结果为止。
职责链模式的原理:
作用域链:查找变量时,先从当前上下文的变量对象中查找,如果没有找到,就会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象; 原型链:当读取实例的属性时,如果找不到,就会查找当前对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止; 事件冒泡: 事件在 DOM 元素上触发后,会从最内层的元素开始发生,一直向外层元素传播,直到全局 document 对象;
优点
- 和命令模式类似,由于处理请求的职责节点可能是职责链上的任一节点,所以请求的发送者和接受者是解耦的;
- 通过改变链内的节点或调整节点次序,可以动态地修改责任链,符合开闭原则;
缺点
- 并不能保证请求一定会被处理,有可能到最后一个节点还不能处理;
- 调试不便,调用层次会比较深,也有可能会导致循环引用;
职责链模式的适用场景:
- 需要多个对象处理同一个请求,具体该请求由哪个对象处理在运行时才确定;
- 在不明确指定接收者的情况下,向多个对象中的其中一个提交请求的话,可以使用职责链模式;
- 如果想要动态指定处理一个请求的对象集合,可以使用职责链模式;
感觉用起来不太顺手,大佬们的示例代码感觉用其他模式更容易实现一点,有些为了用而用的感觉在,等以后有了更深的理解再回来补充吧。
参考文档
转载自:https://juejin.cn/post/7177673745903927353