提供一种管理实体关系的方式
- Proxy(代理):让我们能够在用户访问另一个对象时做出管控
- Decorator(修饰器):能够动态地增强现有对象的行为
Adapter(适配器):让我们能够访问到与某对象接口不同的另一个对象所具备的功能
Proxy(代理)模式
当用户(Client)通过 proxy 来操作 subject 的时候,proxy 会把用户想要对 subject 所执行的全部操作(或其中某些操作)拦截下来,以便增强或补充该操作的功能。
proxy 对象与 subject 对象的接口是相同的,两者是可以互换的。
可以在以下情况使用:验证数据
- 确保安全:是否有权限执行操作
- 实现缓存机制
- 惰性初始化:如果 subject 对象创建起来开销比较大,那就让 proxy 尽量推迟 subject 的创建时机,只有在必须用到 subject 的时候,才去真正创建它
- 实现日志记录功能
-
怎样实现 Proxy 模式
```typescript class StackCalculator { constructor () { this.stack = [] }
putValue (value) { this.stack.push(value) }
getValue () { return this.stack.pop() }
peekValue () { return this.stack[this.stack.length - 1] }
clear () { this.stack = [] }
divide () { const divisor = this.getValue() const dividend = this.getValue() const result = dividend / divisor this.putValue(result) return result }
multiply () { const multiplicand = this.getValue() const multiplier = this.getValue() const result = multiplier * multiplicand this.putValue(result) return result } }
<a name="rwM7K"></a>
### 通过组合对象来实现 Proxy 模式
> 组合(composition)是把某对象与另一个对象相结合,让后者能够扩充或使用前者的功能。
Proxy 模式中,是创建一个新的对象(proxy 对象),让它的接口跟原对象(subject 对象)相同,并且让 proxy 对象通过实例变量或闭包变量来保存 subject,可在创建 proxy 对象时注入,或由 proxy 自己创建。
```typescript
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')
}
// if valid delegates to the subject
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 safeCalculator = new SafeCalculator(calculator)
calculator.putValue(3)
calculator.putValue(2)
console.log(calculator.multiply()) // 3*2 = 6
safeCalculator.putValue(2)
console.log(safeCalculator.multiply()) // 6*2 = 12
calculator.putValue(0)
console.log(calculator.divide()) // 12/0 = Infinity
safeCalculator.clear()
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')
}
// if valid delegates to the subject
return calculator.divide()
},
// delegated methods
putValue (value) {
return calculator.putValue(value)
},
getValue () {
return calculator.getValue()
},
peekValue () {
return calculator.peekValue()
},
clear () {
return calculator.clear()
},
multiply () {
return calculator.multiply()
}
}
}
const calculator = new StackCalculator()
const safeCalculator = createSafeCalculator(calculator)
通过增强原对象来实现 Proxy 模式
在原对象只有少数几个方法需要代理时,通过直接修改 subject,替换相应的方法为我们实现的代理方法
function patchToSafeCalculator (calculator) {
const divideOrig = calculator.divide
calculator.divide = () => {
// additional validation logic
const divisor = calculator.peekValue()
if (divisor === 0) {
throw Error('Division by 0')
}
// if valid delegates to the subject
return divideOrig.apply(calculator)
}
return calculator
}
用内置的 Proxy 对象实现 Proxy 模式
const proxy = new Proxy(target, handler);
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')
}
// if valid delegates to the subject
return target.divide()
}
}
// delegated methods and properties
return target[property]
}
}
const calculator = new StackCalculator()
const safeCalculator = new Proxy(calculator, safeCalculatorHandler)
用内置的 Proxy 做出来的这种对象,会继承 subject 的 prototype(原型),因此,判断
safeCalculator instanceof StackCalculator
所得到的结果是 true
创建带有日志功能的 Writable 流
export function createLoggingWritable (writable) {
return new Proxy(writable, { // ①
get (target, propKey, receiver) { // ②
// 判断受访问属性
if (propKey === 'write') { // ③
return function (...args) { // ④
const [chunk] = args
console.log('Writing', chunk)
return writable.write(...args)
}
}
return target[propKey] // ⑤
}
})
}
import { createWriteStream } from 'fs'
import { createLoggingWritable } from './logging-writable.js'
const writable = createWriteStream('test.txt')
const writableProxy = createLoggingWritable(writable)
writableProxy.write('First chunk')
writableProxy.write('Second chunk')
writable.write('This is not logged')
writableProxy.end()
用 Proxy 实现 Change Observer 模式
Change Observer 模式让某对象(subject)把状态的变化情况通知一个或多个 observe(观察者),令它们能够在 subject 的状态发生改变时尽快做出反应。
和 Observer 模式的区别:
- Change Observer 的重点在于让我们能够监测对象属性上面的变化
Observer 模式采用 EventEmitter 播报信息,让我们知道系统里面发生了什么事件,这个事件不一定是属性上的变化
export function createObservable (target, observer) {
const observable = new Proxy(target, {
set (obj, prop, value) {
if (value !== obj[prop]) {
const prev = obj[prop]
obj[prop] = value
// 对象发生变化时触发的函数
observer({ prop, prev, curr: value })
}
return true
}
})
return observable
}
```typescript import { createObservable } from ‘./create-observable.js’
function calculateTotal (invoice) { // ① return invoice.subtotal - invoice.discount + invoice.tax }
const invoice = {
subtotal: 100,
discount: 10,
tax: 20
}
let total = calculateTotal(invoice)
console.log(Starting total: ${total}
)
const obsInvoice = createObservable( // ②
invoice,
({ prop, prev, curr }) => {
total = calculateTotal(invoice)
console.log(TOTAL: ${total} (${prop} changed: ${prev} -> ${curr})
)
}
)
// ③ obsInvoice.subtotal = 200 // TOTAL: 210 obsInvoice.discount = 20 // TOTAL: 200 obsInvoice.discount = 20 // no change: doesn’t notify obsInvoice.tax = 30 // TOTAL: 210
console.log(Final total: ${total}
)
<a name="GIML7"></a>
# Decorator(修饰器)模式
> 能够动态地扩充现有对象的行为。主要是给其中添加新的功能。
![image.png](https://cdn.nlark.com/yuque/0/2022/png/651859/1653713276473-8cad0dd0-31ca-4e86-bcca-c717677854e0.png#clientId=u9d74d998-fd1b-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=420&id=u930ddf03&margin=%5Bobject%20Object%5D&name=image.png&originHeight=462&originWidth=1395&originalType=binary&ratio=1&rotation=0&showTitle=false&size=69214&status=done&style=none&taskId=ua25df74e-6be9-46ef-ad17-aad85e4bf11&title=&width=1268.181790694718)
<a name="H0mt0"></a>
## 实现 Decorator 模式的几种办法
<a name="JE4XN"></a>
### 用组合实现 Decorator 模式
把受装饰的组件包裹在一个新的对象里面,并让这个新的对象所在的类,支持原组件所具备的那套功能。
```typescript
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')
}
// if valid delegates to the subject
return this.calculator.divide()
}
// ...
}
通过增强原对象来实现 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')
}
// if valid delegates to the subject
return divideOrig.apply(calculator)
}
return calculator
}
const calculator = new StackCalculator()
const enhancedCalculator = patchCalculator(calculator)
通过 Proxy 对象实现 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')
}
// if valid delegates to the subject
return target.divide()
}
}
// delegated methods and properties
return target[property]
}
}
const calculator = new StackCalculator()
const enhancedCalculator = new Proxy(calculator, enhancedCalculatorHandler)
使用 Decorator 模式来修饰 LevelUP 数据库
export function levelSubscribe (db) {
db.subscribe = (pattern, listener) => { // ①
db.on('put', (key, val) => { // ②
const match = Object.keys(pattern).every(
k => (pattern[k] === val[k]) // ③
)
if (match) {
listener(key, val) // ④
}
})
}
return db
}
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'
import level from 'level'
import { levelSubscribe } from './level-subscribe.js'
const __dirname = dirname(fileURLToPath(import.meta.url))
const dbPath = join(__dirname, 'db')
const db = level(dbPath, { valueEncoding: 'json' }) // ①
levelSubscribe(db) // ②
db.subscribe( // ③
{ doctype: 'tweet', language: 'en' },
(k, val) => console.log(val)
)
db.put('1', { // ④
doctype: 'tweet',
text: 'Hi',
language: 'en'
})
db.put('2', {
doctype: 'company',
name: 'ACME Co.'
})
Adapter(适配器)模式
Adapter 模式让我们能够通过一套与原对象不同的接口来访问原对象的功能
通过 fs 式的 API 来使用 LevelUP
构建一个针对 LevelUP API 的 adapter (适配器),让它把那套接口转换成一套与 fs 这个核心模块相兼容的接口。
保证程序每次在 adapter 上面调用 readFile() 与 writeFile() 时,能够把这些操作转换为 db.get() 与 db.put() 操作。
能够让 LevelUP 数据库充当存储后端(storage backend),并利用适配器将其转化成一种支持 fs 接口的对象,使得程序能够在这个对象上面,通过简单的文件操作来修改后端数据库中的内容。
import { resolve } from 'path'
export function createFSAdapter (db) {
return ({
// 确保该函数的接口与 fs 模块所设计的同名函数相兼容
readFile (filename, options, callback) {
if (typeof options === 'function') {
callback = options
options = {}
} else if (typeof options === 'string') {
options = { encoding: options }
}
db.get(resolve(filename), { // ①
valueEncoding: options.encoding
},
(err, value) => {
if (err) {
if (err.type === 'NotFoundError') { // ②
err = new Error(`ENOENT, open "${filename}"`)
err.code = 'ENOENT'
err.errno = 34
err.path = filename
}
return callback && callback(err)
}
callback && callback(null, value) // ③
})
},
writeFile (filename, contents, options, callback) {
if (typeof options === 'function') {
callback = options
options = {}
} else if (typeof options === 'string') {
options = { encoding: options }
}
db.put(resolve(filename), contents, {
valueEncoding: options.encoding
}, callback)
}
})
}
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'
import level from 'level'
import { createFSAdapter } from './fs-adapter.js'
const __dirname = dirname(fileURLToPath(import.meta.url))
const db = level(join(__dirname, 'db'), {
valueEncoding: 'binary'
})
const fs = createFSAdapter(db)
fs.writeFile('file.txt', 'Hello!', () => {
fs.readFile('file.txt', { encoding: 'utf8' }, (err, res) => {
if (err) {
return console.error(err)
}
console.log(res)
})
})
// try to read a missing file
fs.readFile('missing.txt', { encoding: 'utf8' }, (err, res) => {
console.error(err)
})
可以用类似的技术,让某些使用 fs 式 API 的代码,能够同时在 Node.js 平台与浏览器平台运行。