# 面向编程之设计模式
# 设计原则
# 单一职责原则SRP
单一职责原则的定义是‘引起变化的原因’,如果你有两个原因去修改一个方法,说明这个方法有两个职责,承担的职责越多你需要修改他的可能性就越大,修改代码总是件危险的事情,特别是两个职责耦合在一起的时候一句话概括:一个对象(方法)只做一件事
TIP
【优点】:降低了单个类或者对象的复杂度,按照职责把对象分解成更小的粒度,这有助于代码的复用,也有利于进行单元测试。当一个职责需要变更的时候,不会影响到其他的职责。
【缺点】:增加编码复杂度,同时增加对象之间联系的难度
# 最少知识原则LKP
最少知识原则要求我们在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不 必彼此直接通信,那么这两个对象就不要发生直接的相互联系。常见的做法是引入一个第三者对 象,来承担这些对象之间的通信作用。如果一些对象需要向另一些对象发起请求,可以通过第三 者对象来转发这些请求
TIP
【优点】:减少或者消除对象之间的耦合程度,提高复用性 【缺点】:需要封装对象或者引入一个第三者对象来处理两者联系,第三者需要维护
# 开放-封闭原则OCP
当需要改变一个程序的功能或者给这个程序增加新功 能的时候,可以使用增加代码的方式,但是不允许改动程序的源代码
TIP
【优点】:程序的稳定性提升、容易变化的地方分离出来后更容易维护 【缺点】:代码的完全封闭几乎不可能,谁也没有’未卜先知‘的能力,但是我们可以尽可能的去容易变化和不容易变化的地方,挑出容易变化的地方进行封闭 【应用】:用对象的多态性消除条件分支
多态指的是:同一操作在不同的对象上展现出不同的结果 多态最根本的作用就是通过把过程化的条件分支语句转化为对象的多态性,从而消除这些条件分支语句
# 常用设计模式
# 构造函数模式
var obj = new Object()
# 单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点
TIP
【优点】:唯一实例,节约内存开销 【缺点】:违背单一职责原则,不好维护,不容易扩展。不适合容易变化的实例。
例子: 在给命名空间赋值的时候,不是直接赋值给一个对象,而是先执行匿名函数,形成一个私有作用域,在这个作用域中创建一个堆内存,把堆内存地址赋值给命名空间。
var nameSpace = (function () {
var n =12;
function fn() {
}
return {
fn: fn
}
})()
2
3
4
5
6
7
8
9
// 实现一个全局唯一的模态框
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#modal {
height: 300px;
width: 300px;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
border: 1px solid black;
}
</style>
</head>
<body>
<div id="open">打开</div>
<div id="close">关闭</div>
<script>
// 点击打开模态框
document.getElementById('open').addEventListener('click',function(){
const modal = createModal()
modal.style.display = 'block'
})
document.getElementById('close').addEventListener('click',function(){
const modal = createModal()
if(modal) {
modal.style.display = 'none'
}
})
// 采用闭包思路来实现
const createModal = (function() {
let modal = null
return function() {
if(!modal) {
modal = document.createElement('div')
modal.innerHTML = '用闭包思想创建模态框'
modal.id = 'modal'
modal.style.display = 'none'
document.body.appendChild(modal)
}
return modal
}
})()
</script>
</body>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 代理模式
代理模式是为一个对象提供一个代用品或者占位符,以便控制对它的访问
【应用】:图片懒加载,缓存代理
# 策略模式
【应用】:利用配置参数可以减少很多分支当你的代码里有很多if else
或者switch
的时候,就该考虑一下策略模式
- 各判断条件下的策略相互独立且可复用
- 策略内部逻辑相对复杂
- 策略需要灵活组合
# 状态模式
状态模式跟策略模式在实现上有一定的相似之处,但是目的不一样,策略模式每条策略都是平行平等的,而状态模式最大的区别是所有状态都是预先定义好的
# 享元模式
【应用】:如果考虑需要复用元对象的话,建议定义好元对象,然后使用的地方深拷贝或者浅拷贝一下再使用,这样其他地方也可以使用而不破坏元对象啦
# 装饰者模式
装饰者模式可以动态地给某个对象添加一些额外的职责装饰者模式能够在不改变对象自身的基础上,在程序运行期间给对象动态地添加职责
# 发布-订阅者模式
// 实现一个EventEmitter,承担全局事件总线功能
// 实现on事件监听功能
// 实现emit事件订阅功能
class eventEmitter {
constructor() {
// handlers是一个map,用于存储事件和回调之间的对应关系
this.handlers = {}
}
// on方法用于安装事件监听器,接受目标事件名和回调函数作为参数
on(eventName,cb) {
// 先检查一下目标事件名有没有对应的监听函数队列
if(!this.handlers[eventName]) {
//如果没有,那么首先初始化一个监听函数队列
this.handlers[eventName] = []
}
// 把回调函数推入目标事件的监听函数队列里去
this.handlers[eventName].push(cb)
}
//emit用于触发目标事件,接受事件名和监听函数入参作为参数
emit(eventName,...args) {
// 检查目标事件是否有监听函数队列
if(this.handlers[eventName]) {
// 如果有,则逐个调用队列里的回调函数
this.handlers[eventName].forEach((callback) => {
callback(...args)
})
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 观察者模式
观察者模式:一个被观察对象对应多个观察者,两者直接联系;被观察者改变时直接向所有观察者发送消息(调用观察者的更新方法) 观察者模式 与 发布-订阅模式 都是 消息传递的一种实现方式,用来实现 对象间的通信(消息传递),只是发布订阅模式可以看做是观察者模式的 升级版,避免了 被观察对象与观察者之间的直接联系。