JavaScript 结构型设计模式

结构型设计模式: 提供一种管理实体的方式

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: 提供的接口, 是一套与原对象不同的接口

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值