结构型设计模式: 提供一种管理实体
的方式
Proxy 代理模式
proxy: 一个对象, 管控用户对 subject
的访问行为
proxy
会把用户想要对 subject
所执行的操作全部拦截下来
用一个 poxy
对象, 把 subject
的实例包裹取来, 让用户通过 proxyu
去访问 subject
, 而不要直接访问 subject
使用场景: 1️⃣ 数据验证 2️⃣ 权限判断 3️⃣ 缓存 4️⃣ 惰性初始化 5️⃣ 日志记录
通过组合方式实现 Proxy 模式
一个类或一个函数接受一个实例
, 之后使用那个实例所提供的方法(并在此基础上扩展)
实现一个简单的除法计算器 StackCalculator
class StackCalculator {
constructor() {
this.stack = []
}
putValue(value) {
this.stack.push(value)
}
getValue() {
return this.stack.pop()
}
peekValue() {
return this.stack[this.stack.length - 1]
}
divide() {
const divisor = this.getValue()
const dividend = this.getValue()
const result = dividend / divisor
this.putValue(result)
return result
}
}
const calculator = new StackCalculator()
calculator.putValue(12)
calculator.putValue(0)
console.log(calculator.divide()) // 12/0 = Infinity
给类的 constructor 添加实例的方式
通过给 SafeCalculator
传入 StackCalculator
的实例, 并增强其行为的方式, 当其除数为 0 的时候, 抛出错误
class SafeCalculator {
constructor(calculator) {
this.calculator = calculator
}
// proxied method
divide () {
// additional validation logic
const divisor = this.calculator.peekValue()
if (divisor === 0) {
throw Error('Division by 0')
}
return this.calculator.divide()
}
putValue(value) {
return this.calculator.putValue(value)
}
getValue() {
return this.calculator.getValue()
}
peekValue() {
return this.calculator.peekValue()
}
}
const calculator = new StackCalculator()
const safeCalculator = new SafeCalculator(calculator)
safeCalculator.putValue(4)
safeCalculator.putValue(0)
console.log(safeCalculator.divide()) // 4/0 -> Error('Division by 0')
使用工厂函数的方式
另一种写法: 通过工厂函数
与对象字面量
来创建 proxy
对象
function createSafeCalculator (calculator) {
return {
// proxied method
divide () {
// additional validation logic
const divisor = calculator.peekValue()
if (divisor === 0) {
throw Error('Division by 0')
}
return calculator.divide()
},
// delegated methods
putValue (value) {
return calculator.putValue(value)
},
getValue () {
return calculator.getValue()
},
peekValue () {
return calculator.peekValue()
}
}
}
const calculator = new StackCalculator()
const safeCalculator = createSafeCalculator(calculator)
safeCalculator.putValue(4)
safeCalculator.putValue(0)
console.log(safeCalculator.divide()) // 4/0 -> Error('Division by 0')
monkey patching 猴子补丁
Object augmentation 对象增强 也叫做 monkey patching 猴子补丁
只有少数几个方法需要做代理, 可直接修改传入的实例, 并将其返回出去
function patchToSafeCalculator (calculator) {
const divideOrig = calculator.divide
calculator.divide = () => {
// additional validation logic
const divisor = calculator.peekValue()
if (divisor === 0) {
throw Error('Division by 0')
}
console.log('改变了原有实例的逻辑')
return divideOrig.apply(calculator)
}
return calculator
}
const calculator = new StackCalculator()
const safeCalculator = patchToSafeCalculator(calculator)
safeCalculator.putValue(4)
safeCalculator.putValue(0)
console.log(safeCalculator.divide()) // 4/0 -> Error('Division by 0')
calculator.putValue(12)
calculator.putValue(20)
console.log(calculator.divide()) // 将 safeCalculator 注释, 输出: 改变了原有实例的逻辑 \n 0.6
⚠️ 该方法会直接修改传入实例的逻辑, 前两种写法只是在实例外面包了一层, 而没有改变其本身逻辑
Proxy Api
const safeCalculatorHandler = {
get: (target, property) => {
if (property === 'divide') {
// proxied method
return function () {
// additional validation logic
const divisor = target.peekValue()
if (divisor === 0) {
throw Error('Division by 0')
}
return target.divide()
}
}
return target[property]
}
}
const calculator = new StackCalculator()
const safeCalculator = new Proxy(calculator, safeCalculatorHandler)
console.log(safeCalculator instanceof StackCalculator) // true!
safeCalculator.putValue(4)
safeCalculator.putValue(0)
console.log(safeCalculator.divide()) // 4/0 -> Error('Division by 0')
观察者模式
定义一个对象, 当其状态发生变化
, 通过一组 observer (观察者)
即执行一个或多个函数 (观察者可称为effect function 副作用函数
)
逐个判断一系列文件, 看看每个文件里面有没有与正则表达式相匹配的文本, 每发现一次, 通知所有订阅者
import { EventEmitter } from 'events'
import { readFile } from 'fs'
function findRegex(files, regex) {
const emitter = new EventEmitter()
for (const file of files) {
readFile(file, 'utf8', (err, content) => {
if (err) {
return emitter.emit('error', err)
}
emitter.emit('fileread', file)
const match = content.match(regex)
if (match) {
match.forEach(elem => emitter.emit('found', file, elem))
}
})
}
return emitter
}
findRegex(
['fileA.txt', 'fileB.json'],
/hello \w+/g
)
.on('fileread', file => console.log(`${file} was read`))
.on('found', (file, match) => console.log(`Matched "${match}" in ${file}`))
.on('error', err => console.error(`Error emitted ${err.message}`))
fileA.txt
Look for hello world in this text
fileB.json
{
"find": "hello NodeJS",
"maybe": "two hello you"
}
Change Observer 模式
当某个对象上面的状态发生改变, 通知一个或多个 observer (观察者)
, 令其在对象状态改变时做出回应
- Change Observer 模式的重点是检测对象
属性上面
的变化 - Observer 模式更加宽泛, 采用
EventEmitter
播报信息, 让我们知道系统中发生了什么事件, 这个事件不一定是因为受观察对象
在属性上面
出现的变化
如, 返回一组对象中的所有值之和
const obj = {
a: 1,
b: 2,
c: 3
}
const sumAllNum = obj => Object.values(obj).reduce((pre, cur) => pre + cur)
let total = sumAllNum(obj)
console.log(total) // 6
total 的结果依赖于 data, 当 data 发生变化, 需重新执行 sumAllNum
const createObservable = (obj, observer) => {
return new Proxy(obj, {
set(target, prop, value) {
if (target[prop] !== value) {
target[prop] = value
// 当对象中的数据发生变化, 通知观察者, 执行副作用函数
observer(target)
}
return true
}
})
}
// obj 值发生变化, 重新执行 sumAllNum 函数
const observer = obj => total = sumAllNum(obj)
const proxyObj = createObservable(obj, observer)
// 更改 proxyObj, 值发生变化, 执行副作用函数
proxyObj.a = 2
console.log(total) // 7
Decorator 修饰器模式
动态扩充现有对象的行为
与传统类继承是有区别的, 它所作的扩充, 只是针对明确受到修饰的对象
, 因此不会让该类的所有对象都得到扩充
decorator
扩充了 component
的功能, 添加了一个 methodC
方法, 至于 component
里面原有的那些方法, 则是原样委派过去, 在某些情况下, 也可以拦截下来并执行附加逻辑, 以增强其功能
给类的 constructor 添加实例的方式实现 Decorator
class EnhancedCalculator {
constructor (calculator) {
this.calculator = calculator
}
// new method
// 动态扩充现有对象的行为
add () {
const addend2 = this.getValue()
const addend1 = this.getValue()
const result = addend1 + addend2
this.putValue(result)
return result
}
// modified method
divide () {
// additional validation logic
const divisor = this.calculator.peekValue()
if (divisor === 0) {
throw Error('Division by 0')
}
return this.calculator.divide()
}
// delegated methods
putValue (value) {
return this.calculator.putValue(value)
}
getValue () {
return this.calculator.getValue()
}
peekValue () {
return this.calculator.peekValue()
}
clear () {
return this.calculator.clear()
}
multiply () {
return this.calculator.multiply()
}
}
const calculator = new StackCalculator()
const enhancedCalculator = new EnhancedCalculator(calculator)
enhancedCalculator.putValue(4)
enhancedCalculator.putValue(3)
console.log(enhancedCalculator.add()) // 4+3 = 7
enhancedCalculator.putValue(0)
console.log(enhancedCalculator.divide()) // 14/0 -> Error('Division by 0')
和之前: 通过组合方式实现 Proxy 模式-给类的 constructor 添加实例的方式
几乎一摸一样, 就是在其基础上添加了个 add
方法, 扩充现有对象行为
用 monkey patching 实现 Decorator
function patchCalculator(calculator) {
// new method
calculator.add = function () {
const addend2 = calculator.getValue()
const addend1 = calculator.getValue()
const result = addend1 + addend2
calculator.putValue(result)
return result
}
// modified method
const divideOrig = calculator.divide
calculator.divide = () => {
// additional validation logic
const divisor = calculator.peekValue()
if (divisor === 0) {
throw Error('Division by 0')
}
return divideOrig.apply(calculator)
}
return calculator
}
const calculator = new StackCalculator()
const enhancedCalculator = patchCalculator(calculator)
enhancedCalculator.putValue(4)
enhancedCalculator.putValue(3)
console.log(enhancedCalculator.add()) // 4+3 = 7
enhancedCalculator.putValue(0)
console.log(enhancedCalculator.divide()) // 14/0 -> Error('Division by 0')
通过 Proxy API 实现 Decorator 模式
const enhancedCalculatorHandler = {
get(target, property) {
if (property === 'add') {
// new method
return function add() {
const addend2 = target.getValue()
const addend1 = target.getValue()
const result = addend1 + addend2
target.putValue(result)
return result
}
} else if (property === 'divide') {
// modified method
return function () {
// additional validation logic
const divisor = target.peekValue()
if (divisor === 0) {
throw Error('Division by 0')
}
return target.divide()
}
}
// delegated methods and properties
return target[property]
}
}
const calculator = new StackCalculator()
const enhancedCalculator = new Proxy(calculator, enhancedCalculatorHandler)
console.log(enhancedCalculator instanceof StackCalculator) // true!
enhancedCalculator.putValue(4)
enhancedCalculator.putValue(3)
console.log(enhancedCalculator.add()) // 4+3 = 7
enhancedCalculator.putValue(0)
console.log(enhancedCalculator.divide()) // 14/0 -> Error('Division by 0')
Adapter 适配器模式
转换某个对象, 使其使用范围更广
如下, 一个适配器, 将 220v 电压转为 5 v
class Voltage220V {
outputVoltage() {
const voltage = 220;
return voltage;
}
}
class VoltageAdapter {
constructor(curVoltage) {
this.curVoltage = curVoltage
}
// 专门为了转换而编写的逻辑
outputVoltage() {
const voltage = this.curVoltage.outputVoltage() / 44
return voltage
}
}
const curVoltage = new Voltage220V()
console.log(`当前电压为 ${curVoltage.outputVoltage()}`)
const adapter = new VoltageAdapter(curVoltage)
console.log(`当前电压为 ${adapter.outputVoltage()}`)
区别
Proxy: 提供的接口, 和原对象完全一样
, 只是控制了现有对象的访问情况
Decorator: 提供的接口, 不仅涵盖原对象的接口, 还有所加强
Adapter: 提供的接口, 是一套与原对象不同的接口