一、引言
在 <<设计模式: 可复用面向对象软件的基础>> 一书中的定义是:
在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案
这些模式并不是算法或者具体的实现。它们更像是想法、观点和抽象,辅助你去解决一些特定问题。
二、设计模式
1、单例模式
保证一个类仅有一个实例,并提供一个访问它的全局访问点
// 创建单例
class DatabaseConnection {
constructor() {
if (DatabaseConnection.instance) {
return DatabaseConnection.instance
}
// 初始化数据库连接
this.connection = 'connection'
DatabaseConnection.instance = this
}
getConnection() {
return this.connection
}
}
// 使用单例模式获取数据库连接
const dbConnection1 = new DatabaseConnection()
const dbConnection2 = new DatabaseConnection()
console.log(dbConnection1 === dbConnection2) // true
console.log(dbConnection1.getConnection()) // connection
console.log(dbConnection2.getConnection()) // connection
其实就是实例只会被创建一次,后续都是复用这一个实例
2、策略模式
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换
// 计算奖金
const strategies = {
S: (salary) => {
return salary * 4 // 4倍月工资作为奖金
},
A: (salary) => {
return salary * 2 // 2倍
},
B: (salary) => {
return salary * 1 // 1倍
},
}
const calculateBonus = (level, salary) => {
return strategies[level](salary)
}
calculateBonus('S', 10000) // 40000
calculateBonus('A', 800) // 16000
策略模式至少要由两部分组成:
- 封装了各类算法的策略类
- 接受参数的环境类,然后把请求委托给策略类进行处理
3、代理模式
访问对象时为一个对象提供一个代用品或者占位符,以便控制对它的访问
// proxy 示例
// 原始对象
const obj = {
foo: 1,
}
// 代理后的对象
const newP = new Proxy(obj, {
// 拦截读取
get(target, key) {
console.log(`访问了 ${key}!`)
return target[key]
},
// 拦截设置属性操作
set(target, key, value) {
console.log(`设置了 ${key}!`)
target[key] = value
},
})
newP.foo // 访问了 foo!
newP.foo = 2 // 设置了 foo!
这里利用 ES6 的 Proxy
进行举例,可以发现我们不是直接操作原始的对象,而是对代理后的对象进行操作
Vue3中部分响应式原理就是利用了这个特性,如果想要更详细的了解可以访问我写的另一篇文章,这里不再赘述
vue3响应式方案
4、迭代器模式
提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示
// js 内置了迭代器
const arr = [1, 2, 3]
const iterator = arr[Symbol.iterator]()
iterator.next() // {value: 1, done: false}
iterator.next() // {value: 2, done: false}
iterator.next() // {value: 3, done: false}
iterator.next() // {value: undefined, done: true}
其实Js还有 forEach、map等内置迭代器
,这里就不再一一举例
5、发布订阅模式
又称观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都将得到通知
// EventBus
const EventBus = (function () {
const events = {}
return {
subscribe: function (eventName, callback) {
if (!events[eventName]) {
events[eventName] = []
}
events[eventName].push(callback)
},
publish: function (eventName, data) {
if (!events[eventName]) {
return
}
events[eventName].forEach((callback) => callback(data))
},
}
})()
// 订阅事件
EventBus.subscribe('eventA', (data) => {
console.log(`Event A received: ${data}`)
})
// 发布事件
EventBus.publish('eventA', 'Hello, EventBus!') // Event A received: Hello, EventBus!
订阅者一旦订阅后,发布者发布事件都会通知到 每个订阅者
,但是如果过度使用会导致难以跟踪维护,特别是多个发布者和订阅者嵌套的时候
6、命令模式
指一个执行某些特定事情的命令
// 宏命令 比如回家后通过语音助手执行到家命令
const createCommand = () => {
return {
commandsList: [],
execute: () => {
this.commandsList.forEach((command) => command.execute())
},
add: (command) => {
this.commandsList.push(command)
},
}
}
const command1 = {
execute: () => {
console.log('开空调')
},
}
const command2 = {
execute: () => {
console.log('开电视')
},
}
const macroCommand = createCommand()
macroCommand.add(command1)
macroCommand.add(command2)
macroCommand.execute() // 开空调 开电视
命令模式的特点就是不知道请求的接收者是谁,也不知道被请求的操作是什么
,所以需要借助命令对象来执行传入的命令
7、模板方法模式
模板方法是一种只需要使用继承就可以实现的非常简单的模式
// 每日晚餐模板
class Dinner {
setup() {}
start() {
this.setup()
this.cook() // 做饭
this.eat() // 吃饭
this.washDishes() // 洗碗
}
cook() {}
eat() {}
washDishes() {}
}
class ToDayDinner extends Dinner {
setup() {
console.log('今天吃青椒肉丝')
}
cook() {
console.log('做青椒肉丝')
}
eat() {
console.log('吃青椒肉丝')
}
washDishes() {
console.log('洗碗')
}
}
class TomorrowDinner extends Dinner {
setup() {
console.log('明天吃土豆肉丝')
}
cook() {
console.log('做土豆肉丝')
}
eat() {
console.log('吃土豆肉丝')
}
washDishes() {
console.log('洗盘子')
}
}
const todayDinner = new ToDayDinner()
todayDinner.start()
const tomorrowDinner = new TomorrowDinner()
tomorrowDinner.start()
这是一种典型通过封装来提高扩展性的设计模式
8、享元模式
运用共享技术来有效支持大量细粒度的对象
// 对象池工厂 也就是享元模式
const objectPoolFactory = function (createObjFn) {
const objectPool = []
return {
create: function () {
var obj = objectPool.length === 0 ? createObjFn.apply(this, arguments) : objectPool.shift()
return obj
},
recover: function (obj) {
objectPool.push(obj)
},
}
}
// 创建 DOM 工厂
const iframeFactory = objectPoolFactory(function () {
const iframe = document.createElement('iframe')
document.body.appendChild(iframe)
iframe.onload = function () {
iframe.onload = null
iframeFactory.recover(iframe) // iframe加载完了回收节点
}
return iframe
})
const iframe1 = iframeFactory.create()
iframe1.src = 'https://www.baidu.com'
const iframe2 = iframeFactory.create()
iframe2.src = 'http://www.google.com/'
上面例子中需要反复创建 DOM,就比较适合使用享元模式来缓存对应的 DOM 对象,然后再添加到 DOM 对象上:
9、职责链模式
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的偶合关系,将这些对象练成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止
// 超市折扣 大于300 3折 大于200 5折 大于100 8折
class Discount {
constructor() {
this.discount = 0
this.nextHandler = null
}
setNextHandler(handler) {
this.nextHandler = handler
}
handleRequest(originPrice) {
return originPrice * this.discount
}
}
class DiscountHandler1 extends Discount {
constructor() {
super()
this.discount = 0.3
}
handleRequest(originPrice) {
if (originPrice >= 300) {
return super.handleRequest(originPrice)
}
return this.nextHandler.handleRequest(originPrice)
}
}
class DiscountHandler2 extends Discount {
constructor() {
super()
this.discount = 0.5
}
handleRequest(originPrice) {
if (originPrice >= 200) {
return super.handleRequest(originPrice)
}
return this.nextHandler.handleRequest(originPrice)
}
}
class DiscountHandler3 extends Discount {
constructor() {
super()
this.discount = 0.8
}
handleRequest(originPrice) {
if (originPrice >= 100) {
return super.handleRequest(originPrice)
}
return this.nextHandler.handleRequest(originPrice)
}
}
const discountHandler1 = new DiscountHandler1()
const discountHandler2 = new DiscountHandler2()
const discountHandler3 = new DiscountHandler3()
discountHandler1.setNextHandler(discountHandler2)
discountHandler2.setNextHandler(discountHandler3)
console.log(discountHandler1.handleRequest(250)) // 125
console.log(discountHandler1.handleRequest(150)) // 120
最大的优点就是可以解耦多个处理逻辑之间的复杂关系
10、装饰者模式
可以动态的给某个对象添加一些额外的职责,而不会影响从这个类派生的其他对象
// 原始对象
class Plane {
fire() {
console.log('发射普通子弹')
}
}
// 装饰者给原始对象添加额外的职责
class AtomDecorator {
constructor(plane) {
this.plane = plane
}
fire() {
this.plane.fire()
console.log('发射原子弹')
}
}
const plane = new Plane()
plane.fire() // 发射普通子弹
const atomDecorator = new AtomDecorator(plane)
atomDecorator.fire() // 发射普通子弹 发射原子弹
11、状态模式
区分事物内部的状态,事物内部状态的改变往往会带来事物的行为改变
// 事物内部的状态
class StateContent {
constructor() {
this.state = new StateA(this)
}
setState(state) {
this.state = state
}
request() {
this.state.handle()
}
}
class State {
constructor(context) {
this.context = context
}
handle() {}
}
class StateA extends State {
handle() {
console.log('Change State to B')
this.context.setState(new StateB(this.context))
}
}
class StateB extends State {
handle() {
console.log('Change State to A')
this.context.setState(new StateA(this.context))
}
}
const context = new StateContent()
context.request() // Change State to B
context.request() // Change State to A
context.request() // Change State to B
状态模式定义了状态和行为之间的关系, 缺点是会在系统中定义许多状态类
12、适配器模式
解决两个软件实体间的接口不兼容
class OldPrinter {
print(text) {
console.log(`Old Printer: ${text}`)
}
}
class NewPrinter {
printFormatted(text) {
console.log(`New Printer: ${text}`)
}
}
// 适配器类
class PrinterAdapter {
constructor(newPrinter) {
this.newPrinter = newPrinter
}
print(text) {
this.newPrinter.printFormatted(text)
}
}
// 使用适配器模式
const oldPrinter = new OldPrinter()
const newPrinter = new NewPrinter()
const printerAdapter = new PrinterAdapter(newPrinter)
printerAdapter.print('Hello, Adapter Pattern!') // New Printer: Hello, Adapter Pattern!
主要就是解决两个已有接口之间不匹配的问题
三、结语
文本是最近阅读 《JavaScript设计模式与开发实践》 一书后,总结的部分
文中如有错误,欢迎大家在评论区指正,如果本文对你有帮助, 记得点赞?
和关注❤️
如需转载请标明作者和出处