# 什么是设计模式
对软件设计开发过程中反复出现的某类问题的通用解决方案。是指导思想和方法论,不是特定的代码。
# 分类
- 结构型模式:通过识别系统中组件间的简单关系来简化系统的设计。
- 创建型模式:处理对象的创建,根据实际情况使用合适的方式创建对象。
- 行为型模式:用于识别对象之间常见的交互模式并加以实现。
# 一、结构型模式
# 外观模式
是什么:通过为子系统中的一组接口提供统一的高层接口,使子系统更容易使用。简而言之外观设计模式就是把多个子系统中复杂逻辑进行抽象,从而提供一个更统一、更简洁、更易用的API。
做什么:比如JQuery就把复杂的原生DOM操作进行了抽象和封装,并消除了浏览器之间的兼容问题,从而提供了一个更高级更易用的版本。
怎么做:以封装一个DOM事件绑定/解绑方法为例。
function addEvent(element, event, handler) {
if (element.addEventListener) {
element.addEventListener(event, handler, false)
} else if (element.attachEvent) {
element.attachEvent('on' + event, handler)
} else {
element['on' + event] = handler
}
}
function removeEvent(element, event, handler) {
if (element.removeEventListener) {
element.removeEventListener(event, handler, false)
} else if (element.attachEvent) {
element.detachEvent('on' + event, handler)
} else {
element['on' + event] = null
}
}
# 代理模式
是什么:想要访问某个对象,但是调用该对象的代价比较高(耗时、流程复杂等),可以封装一个代理对象,将对目标对象的调用封装在代理对象中,并对外暴露一个与目标对象相同(或更简单)的调用接口。
做什么:提高调用性能、简化调用复杂度
怎么做:以中介债券中数据合并为例,数据合并时,只关心债券代码和报价,如果合并过则缓存下来下次再有相同的代码和报价就直接返回。
// 缓存代理
function proxyMerge = (function() {
const cache = {}
return function(newData) {
const cacheData = cache[`${newData.code}_${newData.price}`]
if (cacheData) {
return cacheData
}
return merge(newData) // merge函数需要遍历多个长数组,性能较差
}
})()
// 使用
console.log(proxyMerge(newData)) // 代理了:console.log(merge(newData))
# 二、创建型模式
# 工厂模式
是什么:可以看成是一个制造其他对象的对象,制造出的对象也会随着传入工厂对象参数的不同而有所区别。
做什么:提供一种集中化、统一化的方式创建对象,避免了分散创建对象导致的代码重复、灵活性差的问题。
怎么做:其实 jquery 的选择器就是工厂模式实现的,我们通过 $('div')、$('.div')、$('#div') 都可以创建一个jquery dom对象。
class jQuery {
constructor(selector) {
}
html() {
}
append() {
}
}
window.$ = function(selector) {
return new jQuery(selectpr)
}
# 单例模式
是什么:顾名思义,单例模式中Class的实例个数最多为1。当需要一个对象去贯穿整个系统执行某些任务时,单例模式就派上了用场。而除此之外的场景尽量避免单例模式的使用,因为单例模式会引入全局状态,而一个健康的系统应该避免引入过多的全局状态。
做什么:全局提示弹窗(全局唯一)、唯一登录浮窗
怎么做:1. 隐藏Class的构造函数,避免多次实例化; 2. 通过暴露一个 getInstance() 方法来创建/获取唯一实例
// 单例构造器
const SingletonFoo = (function() {
// 隐藏的 Class 构造函数
function Foo() {
// 一些业务代码
}
// 未初始化的单例对象
let foo
return {
// 创建/获取单例对象
getInstance: function() {
if (!foo) {
foo = new Foo()
}
return foo
}
}
})()
// 使用
const foo1 = SingletonFoo.getInstance()
const foo2 = SingletonFoo.getInstance()
console.log(foo1 === foo2) // true
# 三、行为型模式
# 策略模式
是什么:定义一系列的算法,把它们一个个封装起来,可以方便扩展,并可以任意组合使用。
做什么:表单验证
怎么做:1. 封装不同策略的策略组;2. 通过不同组合来实现策略的 Context。
// 表单验证为例
// 封装策略组
var strategies = {
isEmpty: function (value, errorMsg) {
if (value === '' || value === null) {
return errorMsg;
}
},
isMobile: function (value, errorMsg) { // 手机号码格式
if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(value)) {
return errorMsg;
}
},
minLength: function (value, length, errorMsg) {
if (value.length < length) {
return errorMsg;
}
}
};
// 验证 “手机号” 的 Context
function validMobile(dom) {
let value = dom.value
let isEmpty = strategies.isEmpty(value, '手机号不能为空')
let isValid = strategies.isMobile(value,'手机号格式错误')
let errorMsg = isEmpty||isValid
if(errorMsg){
alert(errorMsg);
return false;
}
}
# 观察者模式
是什么:对象间的一种一对多的依赖关系;当被观察者的状态发生改变时,其他观察者皆会得到通知并自动更新。
做什么:
怎么做:
// 被观察者 Subject
class Subject {
constructor() {
this.state = 1
this.observers = [] // 观察者列表
}
setState(state) {
this.state = state
this.notify()
}
add(observer) {
this.observers.push(observer)
}
notify() {
this.observers.forEach(observer => {
observer.update()
})
}
}
// 观察者 Observer
class Observer {
constructor(name, subject) {
this.name = name
this.subject = subject
this.subject.add(this) // 将自己加入观察者列表中
}
update() {
console.log(`now, subject state is ${this.subject.state}, so ${this.name} has updated`)
}
}
const sub = new Subject()
const o1 = new Observer('observer1', sub)
const o2 = new Observer('observer2', sub)
const o3 = new Observer('observer3', sub)
sub.setState(2)
# 发布订阅模式
是什么:发布者发布消息时不会直接把消息发给特定的接收者,而是需要一个中间消息管理器来发送到特定的接收者。所以与观察者模式的区别是:1. 观察者模式只有两个角色 被观察者(发布者) 和 观察者(订阅者),而发布订阅模式还有一个“中间消息管理器”;2. 观察者模式两个角色间是松耦合,而发布订阅模式两个角色间完全解耦。
做什么:“事件驱动”项目中,实现组件间通信
怎么做:
class EventBus { // 中间消息管理器
constructor() {
this.events = {}
}
on(evt, handler) { // 订阅
this.events[evt] = this.events[evt] || []
this.events[evt].push({
})
}
emit(evt, args) { // 发布
if (!this.events[evt]) return
for (let i = 0; i < events[evt].length; i++) {
this.events[evt][i]()
}
}
off(evt) { // 取消订阅
delete this.events[evt]
}
}
// 使用
const bus = new EventBus()
// 订阅
bus.on('evt1', function(val) {
console.log('emit evt1, val is' + val)
})
bus.on('evt2', function(val) {
console.log('emit evt2, val is' + val)
})
// 发布
bus.emit('evt1', '1')
bus.emit('evt2', '2')
← 《图解HTTP》学习笔记 位运算 →