From 25938c60a6c1203c326a753e719faea58730ffba Mon Sep 17 00:00:00 2001 From: ascoders <576625322@qq.com> Date: Sun, 1 Nov 2020 21:41:08 +0800 Subject: [PATCH] 171 --- ...13\346\250\241\345\274\217\343\200\213.md" | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 "171.\347\262\276\350\257\273\343\200\212\350\256\276\350\256\241\346\250\241\345\274\217 - Singleton \345\215\225\344\276\213\346\250\241\345\274\217\343\200\213.md" diff --git "a/171.\347\262\276\350\257\273\343\200\212\350\256\276\350\256\241\346\250\241\345\274\217 - Singleton \345\215\225\344\276\213\346\250\241\345\274\217\343\200\213.md" "b/171.\347\262\276\350\257\273\343\200\212\350\256\276\350\256\241\346\250\241\345\274\217 - Singleton \345\215\225\344\276\213\346\250\241\345\274\217\343\200\213.md" new file mode 100644 index 00000000..011bb804 --- /dev/null +++ "b/171.\347\262\276\350\257\273\343\200\212\350\256\276\350\256\241\346\250\241\345\274\217 - Singleton \345\215\225\344\276\213\346\250\241\345\274\217\343\200\213.md" @@ -0,0 +1,115 @@ +# Singleton(单例模式) + +Singleton(单例模式)属于创建型模式,提供一种对象获取方式,保证在一定范围内是唯一的。 + +**意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。** + +其实单例模式在前端体会的不明显,原因有: + +1. 前端代码本身在单机运行,创建的任何变量都是天然分布式的,不需要担心影响另一个用户。 +2. 后端代码是一对多的,分辨出哪些资源是请求间共享的,哪些是请求内独有的很重要。 + +另外我们说到单例,是隐含了一个范围的,指的是在某个范围内单例,比如在一个上下文中,还是一个房间中,还是一个进程,一个线程中单例,不同场景范围会不同。 + +## 举例子 + +如果看不懂上面的意图介绍,没有关系,设计模式需要在日常工作里用起来,结合例子可以加深你的理解,下面我准备了三个例子,让你体会什么场景下会用到这种设计模式。 + +### 多人游戏的共享物品 + +玩过游戏的同学都知道,我们在每局游戏中使用的公共物品在当前房间中是唯一的,但在游戏房间间却不是唯一的,所以这些公共物品肯定有不同的类去描述,那每局游戏中怎么拿公共物品,可以保证拿到的是当前局内唯一的? + +### Redux 数据流 + +其实前端的 Redux 数据流本身就是单例模式,在一个应用中,数据是唯一的,但可以有不同的 UI 使用这份唯一的数据,甚至把一个表格组件展示在两个不同地方,比如全屏模式,但数据依然是一份,我们没有必要为了全屏展示表格,就让它再发一次取数请求,完全可以和原来的表格共享一份数据。 + +### 数据库连接池 + +每个 SQL 查询都依赖数据库连接池,如果每次查询都建立一次数据库连接池,则建立连接的速度会远远慢于 SQL 查询速度,因此你会怎么设计数据库连接池的获取方法? + +## 意图解释 + +单例模式的意图很简单,几乎就是其字面含义: + +**意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。** + +对于多人游戏的共享物品,比如一口锅,要保证在一局游戏内唯一,就要提供一种方法访问到唯一实例。 + +Redux 数据流的 `connect` 装饰器就是全局访问点的一种设计。 + +数据库连接池可以提前初始化好,并通过固定 API 提供这个唯一实例。 + +## 结构图 + + + +`Singleton` 是单例模式的接口,客户只能通过其定义的 `instance()` 访问实例,以保证单例。 + +## 代码例子 + +下面例子使用 typescript 编写。 + +```typescript +class Ball { + private _instance = undefined + + // 构造函数申明为 private,就可以阻止 new Ball() 行为 + private constructor() {} + + public static instance = () => { + if (this._instance === undefined) { + this._instance = new Ball() + } + + return this._instance + } +} + +// 使用 +const ball = Ball.getInstance() +``` + +可以仔细想想,为什么这个例子把单例写成了静态方法,而不是一个全局变量?其实全局变量也能解决问题,但由于会污染全局,要尽可能通过模块化方式解决,上面的例子就是一个较好的封装方式。 + +当然这只是一个最简单的例子,实际上单例模式还有几种模式: + +### 饿汉式 + +初始化时就生成一份实例,这样调用时直接就能获取。 + +### 懒汉式 + +就是代码例子中写的,按需实例化,即调用的时候再实例化。 + +> **要注意,按需不一定是什么好事,如果 New 的成本很高还按需实例化,可能把系统异常的风险留到随机的触发时机,导致难以排查 BUG,另外也会影响第一次实例化时的系统耗时。** + +对 JAVA 来说,单例还需要考虑并发性,有 **双重检测、静态内部类、枚举** 等办法解决,这里不具体展开。 + +## 弊端 + +单例模式的问题有: + +- 对面向对象不太友好。对封装、继承、多态支持不够友好。 +- 不利于梳理类之间的依赖关系。毕竟单例是直接调用的,而不是在构造函数申明的,所以要梳理关系要看完每一行代码才能确定。 +- 可拓展性不好。万一要支持多例就比较难拓展,比如全局数据流可能因为微前端方案改成多实例、数据库连接池为了分治 SQL 改成多实例,都是有可能的,在系统设计之初就要考虑到未来是否还会保持单例。 +- 可测试性不好,因为单例是全局共享的,无法保证测试用例间的隔离。 +- 无法使用构造函数传参。 + +另外单例模式还可以被工厂方法所替代,所以不用特别纠结一种设计模式,可以结合使用,工厂函数也可以内嵌单例模式。 + +## 总结 + +单例模式概念、用法都简单,是架构设计常用方案,但要充分理解到单例模式的弊端,防止不恰当的使用。 + + + + +> 讨论地址是:[精读《设计模式 - Singleton 单例模式》· Issue #278 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/278) + +**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。** + +> 关注 **前端精读微信公众号** + + + +> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh))