设计模式:函数柯里化,发布订阅和观察者模式

84 阅读2分钟

一、函数柯里化

什么是函数柯里化

柯理化,也常以为“局部套用”,是把多参数函数转换为一系列单参数函数并进行调用的技术。 --- 《JavaScript语言精粹》

函数柯里化的好处

  1. 参数复用
  2. 提前返回
  3. 延迟计算/运算

上代码!!!no talking!

柯里化代码实现

例如一个简单的类型判断isType(typing, val),我们通常的调用方式为isType('String','aa'),这种调用方式很容易犯错,很容易将typing输入错误,导致错误的结果。 我们则希望通过类似isString(val)直接判断类型,利用函数柯里化的方式进行封装形成更通用的方式,详见优点一

function isType(typing, val) {
    return Object.prototype.toString.call(val) == `[object ${typing}]`
}

柯里化优点一:参数复用和提高适用性

这里通过传入的参数长度决定调用方式,如果参数长度过短,如只传入typing未传入 val,先将参数缓存起来,直到参数传入完整,再调用整个函数

function curring(fn) {
    const inner = (args = []) => {
        return args.length >= fn.length ? fn(...args) : (...userArgs) => inner([...args, ...userArgs])
    }
    return inner()
}

function isType(typing, val) {
    return Object.prototype.toString.call(val) == `[object ${typing}]`
}

let util = {};
['String', 'Number', 'Boolean', 'Null', 'Undefined'].forEach(type => {
    util['is' + type] = curring(isType)(type)
})

util.isString('a')
util.isNumber('a')
util.isBoolean('a')

tips

这里12-13行代码有个小坑,如果12行末尾不增加;,会出现forEach报错问题,这里涉及到匿名数组的问题,问题详见下面链接。

节点.js - 类型错误:无法读取未定义的属性(读取 'forEach) - 堆栈溢出 (stackoverflow.com)

柯里化优点二:延迟执行

二、发布订阅模式

这是我们的需求:能够在读取a.txt和b.txt后,通知我们读取完成。

高阶函数写法

const fs = require('fs')

let arr = []
function after(times, callback) {
    let arr = []
    return (data, index) => {
        arr[index] = data   //保证顺序,需要索引
        if (--times === 0) { //多个请求并发,需要靠计数器来实现
            callback(arr)
        }
    }
}
let out = after(2, (arr) => {
    console.log(arr)
})

fs.readFile('./a.text', 'UTF8', function (err, data) {
    out(data, 0)
})

fs.readFile('./b.text', 'UTF8', function (err, data) {
    out(data, 1)
})

发布订阅模式写法

const fs = require('fs')

let events = {
    _events: [],
    on(fn) {
        this._events.push(fn)
    },
    emit(data) {
        this._events.forEach(fn => fn(data))
    }
}
events.on(() => {
    console.log('每读取一次 就触发一次')
})
let arr = []
events.on((data) => {
    arr.push(data)
})
events.on((data) => {
    if (arr.length === 2) {
        console.log('读取完毕', arr)
    }
})
fs.readFile('./a.text', 'UTF8', function (err, data) {
    events.emit(data)
})

fs.readFile('./b.text', 'UTF8', function (err, data) {
    events.emit(data)
})

三、观察者模式

需求:存在两种类观察者(爸爸和妈妈),被观察者(宝宝),宝宝状态发生变化的时候,爸爸和妈妈需要能观察到宝宝状态发生了改变

class Subject { //被观察者的类  被观察者  需要将观察者收集起来
    constructor(name) {
        this.name = name
        this.state = '非常开心'
        this.observers = []
    }
    attach(o) {
        this.observers.push(o)
    }
    setState(newState) {
        this.state = newState
        this.observers.forEach(o => o.update(this.name, newState))
    }
}
class Observer {  //观察者
    constructor(name) {
        this.name = name
    }
    update(s, state) {
        console.log(this.name + ":" + s + "当前" + state)
    }
}
let s = new Subject('小宝宝')
let o1 = new Observer('爸爸')
let o2 = new Observer('妈妈')

s.attach(o1)
s.attach(o2)

s.setState('不高兴了')