设计模式
本期主要学习5个
经典
的设计模式,分别是单例模式
,观察者模式
,适配器模式
,策略模式
和装饰器模式
。
以及学习观察者模式
和发布-订阅者模式
在Vue中的应用。
单例模式
创建型模式,提供了一种
创建对象
的最佳方式。
指一个类永远只有一个活跃的实例
,确保只有单个对象被创建。
本质是初始化时,判断是否有
某个实例属性(当前实例对象this
),
有则永远返回
这个实例对象,没有就添加到属性上。
可以减少内存的占用,常常用于管理全局状态和数据
class Logger {
constructor(){
if(Logger.instance){
return Logger.instance
// 如果有实例了,返回这个实例给新对象。
}
Logger.instance = this;
}
log(){
console.log('111')
}
}
const log1 = new Logger();
const log2 = new Logger();
console.log(log1 === log2); //true
log1.log() //111
例如:
Vuex
和Redux
就是基于Flux
, 采用全局单例模式
.
很多第三方库,例如lodash
,jquery
等等,也是一样,多次引用
只会使用同一个对象
。
观察者模式
定义了对象间的
一对多
的依赖
关系,
当一个对象发生改变时,依赖于它的对象会立即得到通知并自动更新。 其中,一
就是被观察者
,负责通知各个观察者
数据更新了。被观察者
将保存所有依赖于它的观察者们
,以便于通知。
// 定义一个被观察者类 -- Subject 类似于广播站
class Subject{
constructor(){
this.observers = [];
// 定义一个属性,数组, 用于保存依赖于它的观察者们。
}
// 定义对应的方法
addObserver(obj){
this.observers.push(obj)
}
removeObserver(obj){
let index = this.observers.indexof(obj);
this.observers.splice(index,1);
}
notifyObserver(msg){
this.observers.forEach(e => {
// 通知每一个观察者,要更新了
// 遍历执行观察者的更新方法。
e.updated(msg);
})
}
}
// 定义一个观察者类
class Observer{
// 自己的更新方法
updated(msg){
console.log(msg);
}
}
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();
// 添加依赖关系 --- 多个观察者依赖于一个被观察者
subject.addObserver(observer1)
subject.addObserver(observer2)
// 通知所有观察者,消息变了
subject.notifyObserver('要更新了');
// 要更新了 x2 打印两次
适配器模式
开发了新的接口以后,为了
兼容旧的接口
,可以使用适配器模式。
本质是将旧接口的实例化对象
,作为新接口的一个属性
,
这样就可以调用该属性,使用旧接口方法。
class OldApp{
doingsomethingOld(){
console.log('旧接口中做旧的事情')
}
}
class NewApp{
constructor(){
this.oldInstance = new OldApp();
}
doingsomethingNew(){
console.log('新接口中做新的事情');
this.oldInstance.doingsomethingOld();
}
}
const newapp = new NewApp();
newapp.doingsomethingNew();
//调用的新接口,但是可以借助属性(继承对象),间接调用旧接口中的方法。
策略模式
本质是将多个策略类的实例,放置到一个控制类中的属性上,
运行控制类的同一个方法时,通过控制类的设置方法切换策略类实例,
灵活的切换策略类,进而触发不同策略类的同名方法。
// 策略类A
class StrategyA{
sayHello(){
console.log('StrategyA...')
}
}
// 策略类B
class StrategyB{
sayHello(){
console.log('StrategyB...')
}
}
// 控制类,外界使用
class Consroller{
// 构造函数上,传入一个实例对象
// 将其他策略纳入,作为一个属性使用。
constructor(strategy){
this.strategy = strategy
}
// 一个切换策略的方法
setStrategy(strategy){
this.strategy = strategy
}
// 一个使用策略类中方法的方法
sayHello(){
this.strategy.sayHello();
}
}
const strategya = new StrategyA();
const strategyb = new StrategyB();
const consroller = new Consroller(strategya);
// 默认是策略a
consroller.sayHello(); //StrategyA...
// 切换策略类
consroller.setStrategy(strategyb);
// 再次调用时,因为切换了属性对应的实例对象,
// 就调用策略b的方法了。
consroller.sayHello(); //StrategyB...
装饰器模式
通过将
原始类
的实例化对象
纳入其中,作为装饰器类
的属性
。
通过调用装饰器实例对象
的方法,间接调用原始类方法
。
用于动态扩展原始类
中的方法,且不与
原始类有任何继承关系
,不修改原始类。
适配器模式也可以扩展,但是适配器模式是事后补救
,且有继承
关系。
例外:装饰器模式是通过调用同名方法
,触发原始类同名方法
。
而适配器模式是通过调用新方法
,触发旧类的旧方法
。
class Origin{
sayHello(){
console.log('Origin...')
}
}
class Decorator{
// 注意这里与适配器模式的区别
/*
constructor(){
this.oldInstance = new OldApp();
}
*/
constructor(origin){
this.origin = origin;
}
// 注意这里一般是同名,与适配器有区别
sayHello(){
this.origin.sayHello();
console.log('Decorator...')
}
}
const origin = new Origin();
const decorator = new Decorator(origin);
decorator.sayHello();
// 'Origin...'
// 'Decorator...'
简要总结
- 单例模式 概念: 永远只返回一个实例对象 实现思路:
- 初始化时,将第一个实例对象作为一个属性(例如instance)。
- 判断instance是否有值,有值就返回这个值作为new的对象。 作用:单向数据流,减少空间占用,管理全局状态和数据,Vuex,Redux, jQuery 等库常见。
- 观察者模式 概念: 一个
一对多
的依赖关系,设计一个被观察者类
,
将所有依赖对象纳入数组中,使用一个属性保存起来。被观察者类
中,可以便捷的通知
所有依赖(观察者)类。 实现思路:
- 初始化时,定义一个属性(例如deps),作为保存依赖的数组。
- 定义添加/删除依赖方法,接收传入
依赖实例
,将依赖添加/删除进deps数组。 - 有更新时,定义通知依赖方法,遍历通知deps所有依赖。 作用:Vue中的Observer类和Watcher类的处理。
- 适配器模式 概念:设计一个
兼容旧接口
的新接口,调用新接口方法时,同时触发旧接口方法。
- 初始化时,定义一个属性(例如oldInstance),保存
旧接口的实例化对象
,本质是new
继承。 - 定义一个新方法,新方法内调用oldInstance的旧接口方法。 作用:当需要设计新接口时,旧接口不想被移除,采用兼容模式补救。
- 策略模式 概念:设计一个控制类,以及切换策略的方法。灵活切换策略类。
- 初始化时,接收一个默认的
策略类实例
作为一个属性(例如strategy) - 定义
设置策略类
的方法,当传入新的策略类实例
, 更换属性值。 - 定义一个方法,触发多个策略类
同名方法
中的某一个 作用:类似于开关,或者委托的思想,一个控制类灵活切换,多个策略类中的同名方法
。
- 装饰器模式 概念:设计一个装饰器类,动态扩展某个
实例对象
的属性和方法。
VS适配器:适配器有继承,内部保存new旧接口,初始化不接收自定义实例
,是为兼容而设计。
VS装饰器:装饰器不含继承关系,是为动态扩展而设计。初始化接收自定义实例
。
- 初始化时,定义一个属性(例如instance), 接收并保存一个
自定义实例化对象
。 - 定义一个方法,内部触发instance的同名方法,
- 接着方法内部,继续添加instance的别的方法或属性,动态扩展。
观察者模式与发布订阅的区别
观察者模式:
- 主要关注
一对多的关系
,- 即一个对象(主题/被观察者)维护一个或多个依赖对象(观察者)。
- 当主题对象状态发生变化时,
- 它会通知所有观察者对象,观察者对象会自动更新。
- 观察者对象一般不知道其他观察者的存在,它们只与主题对象进行交互。
- 观察者模式提供了一种
松耦合
的方式来实现对象间的交互和通信
。 - 例子:一个新闻订阅系统,
- 用户可以订阅不同的新闻频道,当有新的新闻发布时,订阅该频道的用户会收到通知并更新。
发布-订阅模式:
- 通过一个消息中心(或称为事件总线)来实现
- 发布(发布者/发送者)和订阅(订阅者/接收者)之间的解耦,
- 发布者和订阅者不直接相互通信,而是通过
消息中心
进行消息传递。
- 发布者将消息发布到消息中心,订阅者通过订阅感兴趣的消息类型来接收消息。
- 当新的消息发布到消息中心时,订阅了相应消息的订阅者会收到这个消息。
- 发布-订阅模式可以用于解耦复杂的系统组件,让它们彼此独立、灵活地进行通信和交互。
- 例子:一个购物网站提供了不同类型的促销活动,
- 用户可以订阅感兴趣的促销活动,当有新的促销活动发布时,订阅了该活动的用户会收到通知。
关键区别:
- 交互联系:
- 观察者模式中,观察者
直接订阅主题对象
,主题对象直接通知观察者; - 而发布-订阅模式中,发布者和订阅者之间
不直接通信
,而是通过消息中心进行交互。
- 观察者模式中,观察者
- 适用场景:
- 观察者模式更加
简单直接
,适用于一对多的场景; - 而发布-订阅模式可以实现
更松散的耦合
,适用于更复杂的系统组件交互。
- 观察者模式更加
- 通知的同步异步:
- 观察者模式的通知是同步的,
- 即主题对象直接调用观察者对象的方法;
- 而发布-订阅模式的通知是异步的,
- 发布者将消息发布到消息中心,订阅者从消息中心接收消息。
- 观察者模式的通知是同步的,
总结:
- 观察者模式注重对象之间的依赖关系,
- 而发布-订阅模式则以消息中心作为中介实现解耦,适用于更复杂、灵活的系统架构。
- 根据具体需求选择适合的模式。
Vue中的应用
观察者模式
观察者模式机制大大简化了Vue中的
状态管理
和数据驱动视图
的过程。
- Vue的响应式系统:
- 通过使用
Vue.observable
或data
选项定义的数据, - 会被转换成可观察的对象,并建立起观察者和依赖之间的关系。
- Vue通过一个称为
依赖收集
的过程,来跟踪每个可观察对象的依赖关系。 - 当数据发生改变时,触发相应的依赖更新过程。
- 相关的观察者会被通知,并进行更新。
具体细节:
- 当将数据传递给Vue实例时,Vue会遍历这些数据的属性,
- 并在每个属性上使用
Object.defineProperty
来定义getter和setter。 - 在getter中,Vue会收集当前依赖的观察者,并将其添加到依赖列表中。
- 当数据发生改变时,setter会通知依赖列表中的观察者进行更新。
发布-订阅者模式
发布-订阅者模式, 允许组件之间进行
松耦合的通信
和状态共享
。
一些特定的场景下,Vue 也使用了发布-订阅者模式。
- Vue 事件系统:
- Vue 提供了一个全局的事件系统,允许组件之间进行通信。
- 组件可以使用
$emit
方法触发一个自定义事件, - 其他组件通过
$on
方法来订阅该事件并接收通知。 - 这种机制和
发布-订阅者模式
类似。
- VueX State Management:
- VueX 是一个用于状态管理的库,它提供了一个全局的数据仓库,
- 组件可以通过
store
对象访问和修改数据。 - 当一个组件修改了
store
中的数据, - 其他组件可以通过订阅
store
中的相关数据来获取变化的通知。 - 这种机制也符合发布-订阅者模式的思想。
- Vue Router:
- Vue Router 是 Vue.js 官方的路由管理器,
- 用于实现单页应用的路由功能。
- 当路由发生变化时,Vue Router 使用发布-订阅者模式
- 来通知相关的组件进行相应的路由切换和更新。
- Vue 的自定义事件和钩子函数:
- 可以使用自定义事件和生命周期钩子函数,
- 来实现父子组件之间的通信和组件生命周期的控制。
- 这些事件和钩子函数的触发和订阅机制, 也符合发布-订阅者模式。