1. 什么是结构型模式
结构型模式主要用于处理类和对象的组合,对应思维导图:
2. 外观模式: Facade Pattern
对接口二次封装隐藏其复杂性,并简化其使用。
外观模式包含如下角色:
: 外观角色
: 子系统角色
使用时机
当我们将系统分成多个子系统时,我们会降低代码复杂性。编程时的最佳实践是最小化子系统之间的通信和依赖关系。实现这一目标的一个好方法是引入一个对象,为子系统提供单一且统一的接口。
1. 跨浏览器监听事件
要保证处理事件的代码在大多数浏览器下一致运行,需要关注冒泡阶段。
在做跨浏览器网站时,你已经不经意间使用了外观模式:
2. jQuery $(document).ready(..)
我们都熟悉。在源码中,这实际上是一个被调用的方法提供的:
加载事件共用两种方法和
外观模式大量应用于库以让其更容易被使用。譬如我们使用
的或等方法 。
使我们不必手动在内核中调用很多内部方法以便实现某些行为,也同时避免了手动与交互。
类似的还有
3. 适配器模式: Adapter Pattern
传统:适配两个及以上类接口不兼容的问题
: 可额外适配两个及以上代码库、前后端数据等。
使用时机
通常使用适配器的情况:
需要集成新组件并与应用程序中的现有组件一起工作。
重构,程序的哪些部分用改进的接口重写,但旧代码仍然需要原始接口。
内部实现为:
2. Vue中的computed
yck - 《前端面试之道》
在中,我们其实经常使用到适配器模式。
比如父组件传递给子组件一个时间戳属性,组件内部需要将时间戳转为正常的日期显示,一般会使用来做转换这件事情,这个过程就使用到了适配器模式。
4. 代理模式: Proxy Pattern
为其他对象提供一种代理以便控制对这个对象的访问。
可以详细控制访问某个类(对象)的方法,在调用这个方法前作的前置处理(统一的流程代码放到代理中处理)。调用这个方法后做后置处理。
例如:明星的经纪人,租房的中介等等都是代理
使用代理模式的意义是什么?
“单一职责原则”:面向对象设计中鼓励将不同的职责分布到细粒度的对象中,Proxy 在原对象的基础上进行了功能的衍生而又不影响原对象,符合松耦合高内聚的设计理念
遵循“开放-封闭原则”:代理可以随时从程序中去掉,而不用对其他部分的代码进行修改,在实际场景中,随着版本的迭代可能会有多种原因不再需要代理,那么就可以容易的将代理对象换成原对象的调用。
特点:
解决系统之间的耦合度以及系统资源开销大
通过代理对象可保护被代理的对象,使其扩展性不受外界的影响
在js中,它的执行常常依托于浏览器
事件代理就用到了代理模式。
分类:
远程代理():为一个位于不同的地址空间的对象提供一个本地的代理对象
虚拟代理():如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
保护代理():控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
缓冲代理():为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
智能引用代理():当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。
缺点::
由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,例如保护代理。
实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。
前端用得最多的是虚拟代理、保护代理、缓冲代理
1. ES6中的Proxy
所提供构造函数能够让我们轻松的使用代理模式:
2. 图片预加载
目前一般的网站都会有图片预加载机制,也就是在真正的图片在被加载完成之前用一张菊花图(转圈的gif图片)表示正在加载图片。
创建虚拟图片节点并构造创建代理函数:
最后是将原始的图片节点替换为代理图片进行调用:
3. 分页数据:缓存代理
如,前后端分离,向后端请求分页的数据的时候,每次页码改变时都需要重新请求后端数据,我们可以将页面和对应的结果进行缓存,当请求同一页的时候,就不再请求后端的接口而是从缓存中去取数据。
4. 事件代理
事件代理就用到了代理模式。
通过给父节点绑定一个事件,让父节点作为代理去拿到真实点击的节点。
5. 装饰者模式: Decorator Pattern
在不改变原对象的基础上,通过对其进行包装拓展(添加属性或者方法)使原有对象可以满足用户更复杂的需求
装饰器类似于高阶函数的概念。装饰器将基本形式作为参数,并在其上添加处理并将其返回。
优点:
优点是把类(函数)的核心职责和装饰功能区分开了。
问题:
装饰链叠加了函数作用域,如果过长也会产生性能问题。
在中:
装饰者模式提供比继承更有弹性的替代方案。
装饰者用于包装同接口的对象,用于通过重载方法的形式添加新功能,该模式可以在被装饰者的前面或后面加上自己的行为以达到特定的目的。
核心就是缓存上一次的函数
1. 简单例子
举一个简单的例子:
通过一个装饰类,实现了对小明类的装饰。
2. TypeScript 函数修饰符: @
“@”,与其说是修饰函数倒不如说是引用、调用它修饰的函数。
或者用句大白话描述:@: "下面的被我包围了。"
举个栗子,下面的一段代码,里面两个函数,没有被调用,也会有输出结果:
直接运行,输出结果:
3. React 中的装饰器模式
在中,装饰器模式随处可见:
中创建表单的最后一步其实也算装饰器模式
6. 桥接模式:Bridge Pattern
桥接模式将实现层与抽象次层解耦分离,使两部分可以独立变化。
该模式包含如下角色:
(抽象类)
(扩充抽象类)
(实现类接口)
(具体实现类)
常用于应用程序(客户端)和数据库驱动程序(服务):
应用程序写入定义的数据库API,例如,但在此API之后,会发现每个驱动程序的实现对于每个数据库供应商(等)都是完全不同的。
多见于驱动程序开发,在中很少见。
一些软件的跨平台设计有时候也是应用了桥接模式
1. 网站主题替换
在大型网站中,不同模块可能会有不同主题,也有分白天/黑夜 或 用户自主选择的主题。
这时为每个主题创建每个页面的多个副本明显不合理,而桥接模式是更好的选择:
javascript-design-patterns-for-human
不同模块:
以及不同主题:
生成主题:
7. 组合模式: Composite Pattern
又称 部分-整体模式,将对象组合成树形结构以表示“部分整体”的层次结构。
使得用户对单个对象和组合对象的使用具有一致性。(参考卡片和表单组成)
该模式包含以下角色:
- 声明组合中对象的接口并实现默认行为(基于)
- 表示合成中的原始对象
- 在接口中实现与子相关的操作,并存储对象。
1. 操作系统中的文件目录结构
计算机文件结构是组合模式的一个实例。
如果你删除某个文件夹,也将删除该文件夹的所有内容,是吗?这实质上就是组合模式运行原理。你
你可以调用结构树上较高层次的组合对象,消息将沿这一层次结构向下传输。
2. 批量操作DOM
Javascript设计模式理论与实战:组合模式
文档的结构就是天生的树形结构,最基本的元素醉成DOM树,最终形成文档,非常适用适用组合模式。
我们常用的类库,其中组合模式的应用更是频繁,例如经常有下列代码实现:
不论$是一个元素,还是多个元素,最终都是通过统一的和接口进行调用。
我们简单模拟一下的实现:
对于或者是来说,客户端调用都是同样的使用了这个接口,这个就是组合模式的最基本的思想,使部分和整体的使用具有一致性。
8. 享元模式:Flyweight Pattern
享元()模式是一种用于性能优化的模式,“”在这里是苍蝇的意思,意为蝇量级。
主要用于减少创建对象的数量,以减少内存占用和提高性能
运用共享技术来有效支持大量细粒度的对象
享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。在中,浏览器特别是移动端的浏览器分配的内存并不算多,如何节省内存就成了一件非常有意义的事情。
享元模式有以下角色:
客户端:用来调用享元工厂来获取内在数据的类,通常是应用程序所需的对象,
享元工厂:用来维护享元数据的类
享元类:保持内在数据的类
1. 简单例子
在下面的例子中,我们创建了一个“Book”类来处理有关特定书籍,然后创建一个“”类来控制如何创建这些Book对象。
为了获得更好的内存性能,如果同一对象被实例化两次,则会重用这些对象。
2. 在线表格思路实现
打开谷歌在线表格,提取打印其节点元素。
可以看到就算是滚动至千行,它们都只是共用两个视图。
用的就是享元模式,来防止无限滚动造成卡顿。
以下是模拟实现:
首先是HTML
样式:
逻辑实现,请配合注释食用:
9. 结语及参考
至此,结构型设计模式已经讲(水)完了,其中享元模式值得单独拿出来写一篇博客。
领取专属 10元无门槛券
私享最新 技术干货