观察者模式

观察者模式

函数回调


  • 需求

界面上右一个按钮, 点击按钮时, 小娜开始唱歌

var xiaoNa
// 事件回调
function onButtonClick() {
    xiaoNa.sing()
}
<button onClick={onButtonClick}>
</button>

事件回调是最常用的一种异步编程方式.例子中, 我们把回调函数交给button, 就可以安心的等待button的回调.


  • 增加需求

界面面上右一个按钮, 1. 点击按钮时, 小娜开始唱歌;2. 点击按钮时, 小敏开始跳舞

var xiaoNa, xiaoMin
// 事件回调
function onButtonClick() {
    xiaoNa.sing()
    xiaoMin.dance()
}
<button onClick={onButtonClick}>
</button>

观察者模式

xiaoNa和xiaoMin都对button的变化进行响应, 把xiaoNa和xiaoMin抽象成观察者. button则是观察者的目标(被观察者). 有以下观察者模式的实现:

var xiaoNa, xiaoMin
// 观察者列表
var observerList = []
// 添加观察者
function addObserver(observer) {
    observerList.add(observer)
}
// 通知观察者
function notify() {
    for  (Observer  observer : observers)  {
        observer.update()
    }
}
// 添加一个观察者, xiaoNa
addObserver({
    update: () => {
        xiaoNa.sing()
    }
})
// 添加另一个观察者, xiaoMin
addObserver({
    update: () => {
        xiaoMin.dance()
    }
})
// 被观察者
<button onClick={notify}>
</button>

在观察者模式的实现代码中, button的作为目标(被观察者), 维护了一份观察者的列表. 当有观察者关心目标的变化时, 只要加入目标的观察者列表, 当目标变化时, 就可以通知到它.

观察者模式的优点:
1. 目标与观察者间的抽象耦合
这句话有点抽象, 下面解释解释.
解释1: 目标只知道观察者接口, 而不需要知道具体的观察者实现.
解释2: button只需要维护observerList, observer有个update方法, 通知的时候调用update方法即可. 而不需要知道observerList里面具体是xiaoNa, xiaoMin还是xiaoDong, 不需要知道xiaoNa是要sing, 不需要知道xiaoMin是要dance.
解释3: 目标的代码不会因为又多了一个xiaoDong而改变

```
// 普通回调版本
function onButtonClick() {
    xiaoNa.sing()
    xiaoMin.dance()
    xiaoSan.jump()
}
// 观察者模式
addObserver(xiaoNa)
addObserver(xiaoMin)
addObserver(xiaoDong)
```

2. 支持广播通信
这个其实有点废话, 如果是单播直接使用简单的回调就可以了, 就是因为需要广播才使用的观察者模式, 从功能上看, 观察者模式与事件回调的区别就是一个是一对一, 一个是一对多. 在gof设计模式中也这样描述观察者模式:

观察者模式的意图: 定义对象间的一种一对多的依赖关系, 当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新.

观察者模式

观察者模式在redux中的应用

// redux store源码摘要, redux应用了观察者模式, store作为被观察者, 维护了一份观察者列表
store.subscribe(listener) {
    listener.push(listener)
}

// 在dispatch(action), reducer处理完成之后, 通知观察者.
store.dispath(action) {
    currentState = reducer(currentState, action)
    for (var i =for (var i = 0; i < listeners.length; i++) {
      listeners[i]()
    }
}

redux中实现了观察者模式中的被观察者, redux声称其与各种视图框架并不是捆绑在一起的, 可以应用于各种视图框架, 其实就是redux利用了观察者模式, 实现了对观察者的解耦. 在React中使用redux时, 官方提供了react-redux, 结合React与redux的特性实现了对应的观察者.

// react-redux connect源码摘要
// Provider通过React的context功能, 把store通过context传递给children
class Provider extends Component {
    getChildContext() {
        return { [storeKey]: this[storeKey], [subscriptionKey]: null }
    }
    constructor(props, context) {
        super(props, context)
        this[storeKey] = props.store;
    }
    render() {
        return Children.only(this.props.children)
    }
}
// ps: 代码为翻译后的代码, 只保留一些关键部分
function connect(mapStateToProps, mapDispatchToProps, mergeProps) {
    let selector = createSelector(mapStateToProps, mapDispatchToProps, mergeProps)
    // selector整合了mapStateToProps, mapDispatchToProps, mergeProps
    return wrapWithConnect(WrappedComponent) {
        return class Connect extends React.Component {
            constructor(props, context) {
                super(props, context)
                // 获取Provider中通过context传递下来的store
                this.store = props[storeKey] || context[storeKey]
            }
            componentDidMount() {
                // 作为观察者, 监听store.state变化
                this.store.subscribe(this.onStateChange)
                // store.state变化时, 执行selecttor, 判断select的数据是否变化
                this.selector.run(this.props)
                if (this.selector.shouldComponentUpdate) this.forceUpdate()
            }
            // 在props变化时, 执行selecttor, 判断select的数据是否变化
            componentWillReceiveProps(nextProps) {
                this.selector.run(nextProps)
            }
            // selector.run会比对select结果是否变化
            shouldComponentUpdate() {
                return this.selector.shouldComponentUpdate
            }
            // 在store.state变化时, 判断select结果是否变化
            onStateChange() {
                this.selector.run(this.props)
            }
            render() {
                const selector = this.selector
                selector.shouldComponentUpdate = false
                return createElement(WrappedComponent, this.addExtraProps(selector.props))
            }
        }
    }
}

观察者模式Plus

当目标与观察者关系是多对多时
多对多

我们尝试从几个角度来看这个系统:
1. 目标
每个目标都需要自己去维护观察者列表, 有点麻烦.
2. 观察者
需要直接引用目标, 有点耦合.(直接调用了目标的addObserver方法.)
3. 旁观者
这么多目标, 这么多观察者分散在各处, 少的时候还能厘清, 更多的时候就乱成一团了.
这时候可以在目标与观察者之间加入一个事件中心. 事件中心承担了目标对observerList的维护, 观察者转而向事件中心订阅所关心的事件. 目标也是向事件中心发起通知, 由事件中心再转发给观察者.
调度中心

formtastic中的EventCenter就是采用这种模式, 同时维护了目标的列表. formtastic的组件在meta中声明组件的事件, 提供了一个高阶组件, 在组件实例化时把组件以及其会发出的事件注册到EventCenter. 这时候表单编辑器可以从EventCenter中获取可以发送事件的组件列表. 这时候可以通过配置, 选定目标以及所关心的事件, 绑定观察者.

这种在目标与观察者中间加入一个调度中心的方式, 被某些人成为发布订阅模式.
与标准的观察者模式相比, 加入了调度中心之后, 目标与观察者之间的耦合更加松散了. 但是观察者对于事件的筛选需要有一定的过滤规则. 过滤规则可以放在注册时向事件中心提供, 也可以在观察者收到事件之后自行过滤. 具体的实现还是需要根据实际的需求场景进行考虑决择.
所谓的发布订阅模式印证了设计模式并不是一层不变的, 在实践中需要根据实际的需求动态调整具体的实现.

一些实现细节

  • 推模式与拉模式
    观察者模式的实现经常需要让目标广播关于其改变的一些信息. 目标在通知观察者时将这些信息作为参数传递出去, 这些信息的量可能很小, 也可能很大. 这种将信息通过参数传递出去的方式成为推模式.
    由于不同观察者所需要的信息不尽相同, 拉模式则是由观察者自行向目标询问细节.
  • 事件的过滤
    在目标与观察者关系为多对多时, 观察者需要针对不同的目标, 不同的事件作出不同的响应. 因此需要对事件的筛选需要有一定的过滤规则, 特别是加入了调度中心之后, 事件的筛选需要有一定的过滤规则, 过滤规则可以在注册时向调度中心提供, 事件中心在发起通知时根据过滤规则过滤后再通知观察者. 也可以在观察者收到事件之后自行过滤. 具体的实现还是需要根据实际的需求场景进行考虑决择.
  • 让观察者响应注册之前的事件
    某些时候观察者的注册较晚, 但是可能想要响应在它注册之前的事件. 比如组件B可能想监听组件A的didMount事件, 然而组件B的渲染顺序偏偏是在A之后.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值