大家好,这里是大家的林语冰。欢迎关注"前端俱乐部"~
地球人都知道,JS 是一门混合范型语言,所以 JS 也支持面向对象编程,其中设计模式则号称后 ES6 时代 JS 或 TS 面向对象编程的最佳实践。
"设计模式 "源于 GOF(四人帮)合著出版的一部经典作品《设计模式:可复用的面向对象软件元素》,该书第一次完整科普了软件开发中设计模式的概念。
设计模式一般分为三大类:
- 创建型模式
- 结构型模式
- 行为型模式
在 《JS 设计模式速成指南(上)》 中,我们已经科普了前面两大类的设计模式。本期下篇我们将继续补充设计模式最后的一大类别 ------ 行为型模式。
免责声明
本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考。
职责链模式
职责链模式(chain of responsibility)允许沿着有机会处理该请求的对象链传递请求。
收到请求后,每个处理程序决定就地处理该请求,或者将请求传递给职责链中的下一个处理程序。
举个栗子,我们链接 Discount
折扣类,来处理购物车折扣金额的请求。
js
// 购物车类
class ShoppingCart {
constructor() {
this.products = []
}
addProduct(p) {
this.products.push(p)
}
}
// 折扣类
class Discount {
calc(products) {
let ndiscount = new NumberDiscount()
let pdiscount = new PriceDiscount()
let none = new NoneDiscount()
// 创建职责链,链接不同折扣的处理顺序
ndiscount.setNext(pdiscount)
pdiscount.setNext(none)
return ndiscount.exec(products)
}
}
class NumberDiscount {
constructor() {
this.next = null
}
setNext(fn) {
this.next = fn
}
exec(products) {
let result = 0
if (products.length > 3) result = 0.05
return result + this.next.exec(products)
}
}
class PriceDiscount {
constructor() {
this.next = null
}
setNext(fn) {
this.next = fn
}
exec(products) {
let result = 0
let total = products.reduce((a, b) => a + b, 0)
if (total >= 500) result = 0.1
return result + this.next.exec(products)
}
}
class NoneDiscount {
exec() {
return 0
}
}
export { ShoppingCart, Discount }
当存在多个对象可以处理同一请求,且该信息在运行时已知时,请使用职责链模式。
命令模式
命令模式(command)允许将请求封装为对象。
通过此转换,我们可以将请求作为方法参数传递、延迟或排队请求的执行,并支持可撤消的操作。
举个栗子,我们将开/关指令封装为对象,并将它们作为参数传递到 Car
构造函数中。
js
class Car {
constructor(instruction) {
this.instruction = instruction
}
execute() {
this.instruction.execute()
}
}
class Engine {
constructor() {
this.state = false
}
on() {
this.state = true
}
off() {
this.state = false
}
}
class OnInstruction {
constructor(engine) {
this.engine = engine
}
execute() {
this.engine.on()
}
}
class OffInstruction {
constructor(engine) {
this.engine = engine
}
execute() {
this.engine.off()
}
}
export { Car, Engine, OnInstruction, OffInstruction }
当我们有一个需要处理的请求队列,或者我们想要撤消时,请使用命令模式。
解释器模式
解释器模式(interpreter)允许在问题频发时,为简单语言创建语法;可以考虑用简单语言将其表示为一个句子,这样解释器通过解释该句子来解决问题。
举个栗子,我们创建一个简单的数字来对几个数字进行数学运算。
js
class Mul {
constructor(left, right) {
this.left = left
this.right = right
}
interpreter() {
return this.left.interpreter() * this.right.interpreter()
}
}
class Pow {
constructor(left, right) {
this.left = left
this.right = right
}
interpreter() {
return this.left.interpreter() - this.right.interpreter()
}
}
class Num {
constructor(val) {
this.val = val
}
interpreter() {
return this.val
}
}
export { Num, Mul, Pow }
当我们想要解释给定语言,且可以将语句表示为抽象语法树时,请使用解释器模式。
迭代器模式
迭代器模式(iterator)允许读写集合中的元素,而不暴露其底层表示。
举个栗子,我们创建一个简单的迭代器,它将有一个填充了元素的数组,并使用 next()
和 hasNext()
方法,我们可以迭代所有元素。
js
class Iterator {
constructor(el) {
this.index = 0
this.elements = el
}
next() {
return this.elements[this.index++]
}
hasNext() {
return this.index < this.elements.length
}
}
export default Iterator
当我们想要读写对象的内容集合,而不知道其内部表示方式时,请使用迭代器偶模式。
中介者模式
中介者模式(mediator)通过定义封装一组对象如何交互的对象,减少对象间的混乱依赖。
举个栗子,我们创建一个 TrafficTower
中介者类,它允许我们使用 Airplane
实例的所有位置。
js
// 交通信号塔类
class TrafficTower {
constructor() {
this.airplanes = []
}
getPositions() {
return this.airplanes.map(airplane => {
return airplane.position.showPosition()
})
}
}
// 飞机场类
class Airplane {
constructor(position, trafficTower) {
this.position = position
this.trafficTower = trafficTower
this.trafficTower.airplanes.push(this)
}
getPositions() {
return this.trafficTower.getPositions()
}
}
class Position {
constructor(x, y) {
this.x = x
this.y = y
}
showPosition() {
return `My Position is ${x} and ${y}`
}
}
export { TrafficTower, Airplane, Position }
当一组对象之间以复杂的方式通信时,请使用中介者模式。
备忘录模式
备忘录模式(memento)允许捕获并具体化对象的内部状态,这样稍后可以将对象恢复到该状态。
举个栗子,我们创建一种简单方法来存值,并按需恢复快照。
js
class Memento {
constructor(value) {
this.value = value
}
}
const originator = {
store: function (val) {
return new Memento(val)
},
restore: function (memento) {
return memento.value
}
}
class Keeper {
constructor() {
this.values = []
}
addMemento(memento) {
this.values.push(memento)
}
getMemento(index) {
return this.values[index]
}
}
export { originator, Keeper }
当我们想要生成对象状态的快照,以便能够恢复对象的先前状态时,请使用备忘录模式。
观察者模式
观察者模式(observer)允许定义对象间的一对多的依赖关系,这样当一个对象更改状态时,其所有依赖都会得到通知,并自动更新。
举个栗子,我们正在创建一个简单的产品类,其他类可以观察 register()
方法中的注册相关的变更,并且当某些东东更新时,notifyAll()
方法会向所有观察者通信这些更改。
js
class ObservedProduct {
constructor() {
this.price = 0
this.actions = []
}
setBasePrice(val) {
this.price = val
this.notifyAll()
}
register(observer) {
this.actions.push(observer)
}
unregister(observer) {
this.actions.remove.filter(function (el) {
return el !== observer
})
}
notifyAll() {
return this.actions.forEach(
function (el) {
el.update(this)
}.bind(this)
)
}
}
class fees {
update(product) {
product.price = product.price * 1.2
}
}
class profit {
update(product) {
product.price = product.price * 2
}
}
export { ObservedProduct, fees, profit }
当更改一个对象的状态可能需要更改其他对象,并且实际的一组对象事先未知或动态更改时,请使用观察者模式。
状态模式
状态模式(state)允许对象在其内部状态发生变化时,改变其行为。
举个栗子,我们使用 Order
类创建一个简单的状态模式,该类会使用 next()
方法更新其状态。
js
class OrderStatus {
constructor(name, nextStatus) {
this.name = name
this.nextStatus = nextStatus
}
next() {
return new this.nextStatus()
}
}
class WaitingForPayment extends OrderStatus {
constructor() {
super('waitingForPayment', Shipping)
}
}
class Shipping extends OrderStatus {
constructor() {
super('shipping', Delivered)
}
}
class Delivered extends OrderStatus {
constructor() {
super('delivered', Delivered)
}
}
class Order {
constructor() {
this.state = new WaitingForPayment()
}
nextPattern() {
this.state = this.state.next()
}
}
export default Order
当对象的行为取决于其状态,并且其在运行时的行为变化取决于该状态时,请使用状态模式。
策略模式
策略模式(strategy)允许定义一系列算法,封装每个算法,并使它们可以互换。
举个栗子,我们有一组可用于 ShoppingCart
购物车的折扣。
这里的技巧是,我们可以传递要用于构造函数的函数,并以这种方式更改折扣金额。
js
class ShoppingCart {
constructor(discount) {
this.discount = discount
this.amount = 0
}
checkout() {
return this.discount(this.amount)
}
setAmount(amount) {
this.amount = amount
}
}
function guest(amount) {
return amount
}
function regular(amount) {
return amount * 0.9
}
function premium(amount) {
return amount * 0.8
}
export { ShoppingCart, guest, regular, premium }
当我们有一大坨类似的类,它们只是在执行某些行为的方式上有所不同时,请使用策略模式。
模板模式
模板模式(template)允许在超类中定义算法的骨架,但允许子类覆盖算法的特定步骤,而不改变其结构。
举个栗子,我们创建一个简单的模板方法来计算税收,并在增值税和商品及服务税(税收类型)中扩展此模板。这样我们就可以在若干税收类别中,复用相同的结构。
js
class Tax {
calc(value) {
if (value >= 1000) value = this.overThousand(value)
return this.complementaryFee(value)
}
complementaryFee(value) {
return value + 10
}
}
// 增值税
class VAT extends Tax {
constructor() {
super()
}
overThousand(value) {
return value * 1.1
}
}
// 商品及服务税
class GST extends Tax {
constructor() {
super()
}
overThousand(value) {
return value * 1.2
}
}
export { VAT, GST }
当我们想让客户端只扩展算法的特定步骤,而不是整个算法或其结构时,请使用模板模式。
访问者模式
访问者模式(visitor)允许将算法与其操作的对象分开。
举个栗子,我们创建一个结构体,来计算两种员工的奖金,这样我们就可以将奖金方法扩展到越来越多类型的员工,比如 CEO 奖金、VP奖金等。
js
function bonusPattern(employee) {
if (employee instanceof Manager) employee.bonus = employee.salary * 2
if (employee instanceof Developer) employee.bonus = employee.salary
}
class Employee {
constructor(salary) {
this.bonus = 0
this.salary = salary
}
accept(item) {
item(this)
}
}
class Manager extends Employee {
constructor(salary) {
super(salary)
}
}
class Developer extends Employee {
constructor(salary) {
super(salary)
}
}
export { Developer, Manager, bonusPattern }
当对象结构包含一大坨类,并且我们想要操作该结构中依赖于其类的元素时,请使用访问者模式。
高潮总结
行为型模式(behavioral)是一种设计模式类别,适用于通信和对象情况间的分配的相关常见问题。
我们已经亲眼见证设计模式如何为常见问题提供优雅的解决方案,使得代码不仅更易维护,而且更易理解和扩展。
设计模式的优雅之处在于其通用性。无论是构建简单的 Web App,还是复杂的企业系统,设计模式都提供了一种超越 JS 生态系统细节的语言。
本期话题是 ------ 你在实际开发中最常使用的是哪种设计模式?欢迎在本文下方自由言论,文明共享。
长期关注"前端俱乐部",坚持阅读,自律打卡,每天一次,进步一点。谢谢大家的点赞,掰掰~