设计模式是一种书写代码的方式,为了解决特定问题给出的简洁而优化的解决方案
常用的几种设计模式
1.单例模式
核心:一个构造函数一生只能有一个实例,不管你new多少次,都是这一个实例。
应用如:自定义弹出层
我们分析一下如下代码:
function Person(){
this.name = 'jack'
}
const p1 = new Person()
const p2 = new Person()
console.log(p1 === p2) // false
以上代码的问题在于p1 === p2返回true时才表示实现了单例模式
所以我们先来写一个单例模式的核心代码
let instance = null
function singleton() {
if (!instance) instance = 实例对象
return instance
}
加入单例模式核心代码后:
function Person() {
this.name = 'jack'
}
let isntance = null
function singleton() {
if (!instance) instance = new Person()
return instance
}
const p1 = singleton()
// 当第二次调用singleton的时候,此时instance是第一次new出来的实例,if条件就不会执行了,直接返回第一次实例的地址
const p2 = singleton()
console.log(p1 === p2) // true
以上代码的问题:
- 书写代码的时候和构造函数已经没有关系了,直接调用了封装的singleton函数
- new关键字没有了 所以,让我们改造一下单例模式的代码,改造核心如下:
- 需要把instance变量保存下来
- singleton是一个函数,里边可以判断 可以返回
- 以闭包的形式来执行一段代码
- 为了保存构造函数,把它也写到闭包里边
- 如果使用需要new,那么就直接写
const Person = (function() {
// 真实构造函数体
function Person(name, age, gender) {
this.name = 'jack'
this.age = age
this.gender = gender
}
Person.prototype.sayHi = function() {
console.log('hello world')
}
// 这个变量因为在一个不会被销毁的函数执行空间里边,所以会一直存在
let instance = null
return function singleton(...arg) {
if (!instance) instance = new Person(...arg)
return instance
}
})()
const p1 = new Person('jack', 18, '男')
const p2 = new Person('rose', 19, '女') // 此处传参无用
console.log(p1 === p2) // true
2.观察者模式
核心:观察者模式主要是监控一个对象的状态,一旦状态发生变化,就触发一些技能。需要2个构造函数来实现(一个创建观察者,一个创建被观察者)
例子:监控
=》我们坐在教室里,就是被观察者
=》坐在监控后边的老师就是观察者
=》当被观察者出发了一些条件的时候,观察者就会触发一些技能
创建观察者:需要一个身份证明和一个技能
// 观察者构造函数
class Observer {
constructor(name, fn = () => {}) {
this.name = name
this.fn = fn
}
}
// 创建两个观察者实例
const bzr = new Observer('班主任', (state) => { console.log(`因为 ${state} 把你家长找来`) })
const xz = new Observer('校长', (state) => { console.log(`开会`) })
创建被观察者:
- 需要一个属性,记录自己的状态。
- 需要一个队列,记录都有谁观察着自己。数组[]
- 方法:能设置自己的状态,当需要改变状态的时候,触发这个方法。
- 方法:添加观察者。
- 方法:删除观察者。
// 被观察者构造函数
class subject {
constructor(state) {
this.state = state // 记录状态
this.observers = [] // 记录观察着我的人
}
// 设置状态的函数
setState(val) {
this.state = val
// 状态一变,就需要触发所有观察者的技能
// 遍历observers依次触发技能
this.observers.forEach(item => {
// 告诉改变成了什么状态
item.fn(this.state)
})
}
// 添加观察者
addObserver(obs) {
// 谁是观察者就传递谁进来
// 判断一下,如果观察者已存在,就不再添加
this.observers = this.observers.filter(item => item !== obs)
this.observers.push(obs)
}
// 删除观察者
delObserver(obs) {
this.observers = this.observers.filter(item => item !== obs)
}
}
// 创建一个被观察者
const xiaoming = new subject('学习')
// 给xiaoming添加观察者
xiaoming.addObserver(bzr)
xiaoming.addObserver(xz)
console.log(xiaoming)
3.发布订阅模式
核心:
- 有一个对象,有人一直看着它。
- 当这个对象发变化的时候,第三方通知这个看着的人,触发技能。
- 只需要创建一个第三方的构造函数即可。 (addEventListener也属于发布订阅模式,相当于监控着dom元素的变化)
例子:买书
1.普通程序员买书:
=》去书店,问,没有,回家
=》过一会再去,问,没有,回家
=》过一会再去,问,没有,回家
2.发布订阅程序员:
=》去书店,问,没有,留下一个联系方式给店员
=》一旦有了书,就会接到电话
=》触发技能去买书
接下来让我们分析一下构造函数中的内容:
- 需要一个任务队列如:
{
click: [ fn1, fn2 ],
abc: [ fn1, fn2 ],
}
- 方法:能向消息队列里边添加内容
- 方法: 可删除消息队列里边的内容
- 方法:能触发消息队列里面的内容
// 创建一个第三方观察者构造函数
class Observer {
constructor() {
this.message = {} // 存放消息队列
}
// 向消息队列里边添加内容
on(type, fn) {
// type: 拜托看着的行为
// fn: 在行为发生的时候做的事情
// 就应该把这些记录在消息队列里边:
// 判断message里边有没有这个行为被注册过了
// 如果没有直接赋值一个这个行为
// 如果有,直接向数组里添加就可以了
if (!this.message[type]) {
this.message[type] = []
}
// 这块是不会去重重复的操作的。就像可以用addEventListener绑定多个相同的事件一样。一般业务逻辑里也不会写相同的回调此操作,因为没有意义
this.message[type].push(fn)
}
// 删除消息队列里边的内容
off(type, fn) {
// 删除消息分为删除整个事件及删除事件中的某个行为
// 判断如果fn不存在,只有一个参数的情况
if (!fn) {
// 直接把这个事情取消掉
delete this.message[type]
return
}
// 判断是否真的订阅过类型为type的事件
if (!this.message[type]) return
this.message[type] = this.message[type].filter(item => item !== fn)
}
// 触发消息队列里边的内容
emit(type) {
// 判断是否有订阅过
if (!this.message[type]) return
this.message[type].forEach(item => {
item()
})
}
}
// 使用构造函数创建一个实例
const person1 = new Observer()
// 当你想拜托这个person1帮你观察一些内容的时候
// 告诉你一个行为,当这个行为出现的时候,告诉你干什么
person1.on('abc', handel1)
person1.on('abc', handel2)
person1.on('abc', handel3)
console.log(person1)
person1.off('abc', handel1)
console.log(person1)
person1.off('abc')
console.log(person1)
person1.emit('abc')
function handel1() { console.log('handel1') }
function handel2() { console.log('handel2') }
function handel3() { console.log('handel3') }
4.策略模式
核心:
一个问题匹配多个解决方案,不一定要用到哪一个,而且可能随时会继续增加多个方案
例子:购物车结算
=》有多种折扣计算方式
=》100减10
=》纯8折
// 接收两个参数:价格,折扣种类
function calcPrice(price, type) {
if (type === '100-10') {
price -= parseInt(price / 100)*10
} else if (type === '80%') {
price *= 0.8
}
}
// 使用的时候
const res = calcPrice(320, '100-10')
以上代码可实现基本功能,但是问题是每次新增或删除一种策略模式就要手动去修改calcPrice函数源代码。
所以采取以下方案:
- 把多种解决方案用闭包的形式保存起来,按照传递进来的价格和类型判断并计算结果返回
- 对外留添加和删除的接口
const calcPrice = (function() {
const sale = {
'100-10': (price) => { return price -= parseInt(price / 100)*10 },
'80%': (price) => { return price *= 0.8 }
}
function calcPrice(price, type) {
if (!sale[type]) return // 判断对象里有没有折扣类型
return sale[type](price) // 执行计算结果并返回
}
// 把函数当做一个对象,向里边添加一些成员
calcPrice.add = function(type, fn) {
if (sale[type]) return
sale[type] = fn
}
calcPrice.del = function(type) {
delete sale[type]
}
return calcPrice
})()
calcPrice.add('70%', (price) => { return price *= 0.8 })