关于 JavaScript 设计模式

141 阅读10分钟

本文由观看 Javascript 设计模式系统讲解与应用 学习整理而来,观看视频请支持正版。

所有演示, UML 类图均以贴合 javascript 语法为主。

设计原则

设计准则

  1. 小即是美
  2. 让每个程序只做好一件事
  3. 快速建立原型
  4. 舍弃高效率而取可移植性(程序随着硬件升级的兼容性很重要)
  5. 采用纯文本储存数据(数据的可读性很主要)
  6. 软件复用
  7. 允许用户定制环境
  8. 使用过小写字母并尽量简短
  9. 沉默是金(输出的内容需要合理)
  10. 各部分之和大于整体
  11. 寻求 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

示例

  1. 订报纸、订牛奶, 到点会有送报员过来送。
  2. 可以同时订牛奶和报纸。

发布订阅的机制,有能力一对多,不一定是一定要一对多。

场景

网页事件绑定、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 不能相互访问,只能通过中介去访问,例如二手房中介,对应多个卖家和多个买家。

各个关联对象通过中介者隔离,符合开放封闭原则。