本文由观看 Javascript 设计模式系统讲解与应用 学习整理而来,观看视频请支持正版。
所有演示, UML 类图均以贴合 javascript 语法为主。
设计原则
设计准则
- 小即是美
- 让每个程序只做好一件事
- 快速建立原型
- 舍弃高效率而取可移植性(程序随着硬件升级的兼容性很重要)
- 采用纯文本储存数据(数据的可读性很主要)
- 软件复用
- 允许用户定制环境
- 使用过小写字母并尽量简短
- 沉默是金(输出的内容需要合理)
- 各部分之和大于整体
- 寻求 90% 的解决方案
设计准则参考 《UNUX/LINUX设计哲学》
SOLID 五大设计原则
S - 单一职责原则
- 一个程序只做好一件事
- 如果功能过于复杂就拆分开,每个部分保持独立
O - 开放封闭原则
- 对扩展开放,对修改封闭
- 增加需求时,扩展新代码,而非修改已有代码
- 软件设计的终极目标
L - 李氏置换原则
- 子类能覆盖父类
- 父类能出现的地方子类就能出现
I - 接口独立原则
- 保持接口的单一独立,避免出现 “胖接口” (不是说100%不能出现,需要慎重使用)
- JS 中没有接口(typescript 中有接口的定义)
- 类似单一职责原则,这里会更关注接口
D - 依赖导致原则
- 面相接口编程,依赖于抽象而不依赖于具体
- 使用方只关注接口而不关注具体的实现
- JS 中没有接口,使用较少
总结:在 JS 中,S O 体现较多, L I D 体现较少,但需要了解。
从设计到模式
设计模式,应该分开,设计就是设计原则,设计原则就像统一的指导思想,模式根据指导思想,结合开发经验,总结出来的一些既定的模板,两者关联起来,要先了解设计原则,再了解设计模式,从设计到模式。
工厂模式
将 new 操作单独封装
设计原则验证
- 构造函数和创建者分离
- 符合开放封闭原则
理解:
工厂模式就是通过工厂方法,把构造函数(可以理解为具体的实现)和使用者隔离起来。
场景:
Product 是封装好的构造函数,里面是一些业务或者功能的具体实现,再通过 Factory 工厂方法,把 Product 的实例返回,给使用者使用。
单例模式
- 系统中唯一使用
- 一个类 只有一个实例
因为 javaScript 里面没有 private 这里用 ts 来进行代码的演示。
设计原则验证
- 符合单一职责原则,只实例化唯一的对象
- 没法具体开放封闭原则,但是绝对不违反开放封闭原则
理解:
字面意思,只能有唯一的一个实例。
场景:
购物车,vuex store中的状态管理,模块A 和 模块B 在 store 中获取的状态必须是一致的,数据必须是需要能够共享的,这就是单例模式的体现。
适配器模式
- 旧接口格式和使用者不兼容
- 中间加一个适配转换接口
列如变压器,电源适配器,电脑网线转换头等。
设计原则验证
- 将旧接口和使用者进行分离
- 符合开放封闭原则
理解:
适配器模式,类似电源适配器一样,将220V 的电压转换成电脑手机能够使用的电压。适配器里可以进行一些处理,最后得到我们想要的东西。
场景:
一些老方法的改造,列如
这样老的 oldAjax.request() 仍然可以使用,但是实际是用的新的 request 方法。
Vue 的 computed
装饰器模式
- 为对象添加新功能
- 不改变其原有的结构和功能
ES7 装饰器
ES7 装饰器语法需要使用 babel 编译。
装饰类
可以加上参数
mixins 混合
target 是装饰的类,装饰器必须是一个函数。需要装饰哪个类就在它上面写上 @[装饰器方法], 相当于, 在执行的时候,把需要装饰的类,传到装饰器方法里。
装饰方法
core-decorators 第三方开源 lib
core-decorators 会提供一些常用的装饰器,是目前比较好用的库。
类似 readonly 这种常用的都已经提供了
再看一个 deprecate
理解
装饰器模式在不改变原有对象的情况下,去扩展一些功能来满足更加复杂的业务需求。就像给手机装个手机壳,可以保护手机和当支架,但是手机壳仍然会暴露出手机摄像头、充电口、按键,不会覆盖这些手机原有的功能。
设计原则验证
- 将现有对象和装饰器进行分离,两者独立存在
- 符合开放封闭原则
场景
ES7 装饰器提案
代理模式
- 使用者无权访问对象
- 中间加代理,通过代理做授权和控制
ES6 Proxy
这里用明星和经纪人举例演示
原对象的接口和属性,代理后是不会发生变化的,否则就不是代理模式了。
理解
使用者在使用目标类的时候,受限于一些权限或者安全问题,不能直接访问,就需要加代理,通过代理去访问。 就类似疫情期间在家里访问公司内网,需要挂 vpn代理访问。代理的接口或者属性应该和目标类一样,体验上是相同的,比如挂了 vpn 代理,该访问什么地址就是什么地址,不能加了代理之后,访问的地址和接口还变了,那么就不是代理了。
设计原则验证
- 代理类和目标类分离,隔离开目标类和使用者
- 符合开放封闭原则
场景
网页事件代理,ES6 Proxy, jQuery.proxy
代理模式,适配器模式,装饰器模式对比
代理模式 VS 适配器模式
- 适配器模式: 提供一个不同的接口。(使用者可以用,但是因为目标类太老旧或其他原因,不能够直接使用,可以用,但是没法直接用。)
- 代理模式:提供一模一样的接口。(使用者无权使用,但是又想使用,所以要提供一模一样的接口,才能感觉有权使用。)
代理模式 VS 装饰器模式
-
装饰器模式:扩展功能,原有功能不变且可直接使用。(原有的可以用,扩展之后功能增强,扩展的功能和原有功能不冲突。)
-
代理模式:显示原有功能,但是经过限制或者阉割之后的。(直接针对原有功能)
观察者模式
- 发布 & 订阅
- 一对n, n 可以等于 1
示例
- 订报纸、订牛奶, 到点会有送报员过来送。
- 可以同时订牛奶和报纸。
发布订阅的机制,有能力一对多,不一定是一定要一对多。
场景:
网页事件绑定、promise、nodejs 自定义事件等等
网页事件绑定
绑定事件可以理解为订阅,绑定的方法 = 观察者, 当按钮被点击,会触发所有订阅的观察者。
nodejs 自定义事件
观察者模式的场景非常的多,jq 的 callback, vue 的监听和生命周期等等。
理解
当前观察者订阅之后,当主题发生变化或者被触发,则会触发所有订阅在它下面的观察者。类似点了咖啡,坐着等就行了,发了取餐消息,就可以去拿。
设计原则验证
-
主题和观察者分离,不是主动触发而是被动监听,两者解耦
-
符合开放封闭原则
迭代器模式
- 顺序访问一个集合
- 使用者无需知道集合的内部结构
UML 类图和实例不好弄,直接开始说 es6 的迭代器。
ES6 Iterator
- 有 [Symbol.iterator] 属性。
- 属性值是函数,执行函数返回一个迭代器。
- 迭代器有 next 方法顺序迭代子元素。
代码演示
但是迭代器不可能需要所有人都去封装一个遍历的方法,因此有了 for...of
很轻松的遍历出了结果
场景
迭代器可以用来顺序遍历 nodeList, map, set 等具有迭代器特性的数据类型。
理解
顺序去访问集合,通过迭代器的 next 方法访问下一个子元素,嗯,有点像链表。
设计原则验证
-
迭代器对象和目标对象分离
-
迭代器将使用者和目标对象隔开
-
符合开放封闭原则
状态模式
- 一个对象有状态变化
- 每次状态变化都会触发一个逻辑
用 handle 来改变状态,是为了拆开,在 handle 里面可以有其他的逻辑, setState 只是 handle 里面的一部分。
场景
Promise 的状态变化。
理解
状态机,管理状态,和状态对象分离,状态的变化逻辑单独去处理。
设计原则验证
-
将状态对象和主题对象分离,状态的变化逻辑单独处理
-
符合开放封闭原则
其他模式
原型模式
概念
clone 自己,生成一个新对象。
prototype 可以理解为 ES6 class 的一组底层原理,但是 class 是实现面相对象的基础,并不是服务某个模式。
桥接模式
概念
用于把抽象化与实现化解耦,使二者可以独立变化。(js 未找到经典应用)
组合模式
概念
生成树形结构,表示 “整体=部分” 关系,让整体和部分有一致的操作方式, js 经典应用中,没有很复杂的数据类型,用虚拟 DOM 来演示。
整体和单个节点的操作是一致的,整体和单个节点的数据结构也是保持一致的。
将整体和单个节点的操作抽象出来,符合开放封闭原则。
享元模式
概念
共享内存,主要考虑内存而非效率,相同的数据共享使用。js 中使用较少,客户端考虑内存的比较少,常见于服务端。
只能找一个相对贴和享元模式的列子演示
将相同的部分抽象出来,符合开放封闭原则。
策略模式
概念
不同策略分开处理,不混合一起,符合开放封闭原则。
个人理解
类似配置文件,通过找到不同的配置进行不同的处理。
职责链模式
概念
一步操作可能分位多个职责角色来完成,把这些角色都分开,然后用一个链串起来,将发起者和各个处理者进行隔离。
职责链和业务结合较多,用一个审批流程演示。
职责链在 js 中能想到的是链式操作,比如 jq 的链式操作,和 Promise.then 的链式操作。
发起者于各个处理者隔离,符合开放封闭原则。
命令模式
概念
执行命令时,发布者和执行者分开,中间加入命令对象,作为中转站。
将军 =》旗手(传令兵) =》士兵
场景
document.execCommand('copy')
执行复制,只需要传递需要执行的命令,至于命令如何执行实现复制,我们调用是不用管的。
命令对象与执行对象分开,解耦。符合开放封闭原则。
备忘录模式
概念
随时记录一个对象的状态变化,随时可以恢复之前的某个状态。
这里用一个简单的、类似编辑器的备忘和撤销演示
状态对象与使用者分开,解耦。符合开放封闭原则。
中介者模式
概念
中介者一般存在于多对多通讯的场景,主要解决多个模块直接交互混乱的问题,因此弄一个中介者当做中转方。
A 和 B 不能相互访问,只能通过中介去访问,例如二手房中介,对应多个卖家和多个买家。
各个关联对象通过中介者隔离,符合开放封闭原则。