观察者模式、发布订阅模式是同一个模式?

555 阅读5分钟
观察者模式定义

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知依赖它的对象。在观察者模式中,主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。

1.webp

例如:在学习平台上预约了直播,开播就会通知已预约的人准时参加。直播是《被观察者》,预约的同学是《观察者》。

观察者模式UML类图

WX20220430-161154@2x.png

观察者模式角色

从类图中可以看出主要有四个角色。

  • Subject(被观察者) 即被观察的抽象类,包含具体被观察对象所该实现的方法定义,有时它也被直接用作具体类。
  • ConcreteSubject(具体被观察者) 是被观察者抽象类的具体实现,包含被观察的数据的状态,在数据的状态发生时,即可通知列表中的观察者。
  • Observer(观察者) 观察者的接口定义。
  • ConcreteObeserver(具体观察者) 具体的各个观察者类,需要实现update方法,具体被观察者将在内部数据状态改变时通知更新。
观察者模式demo
// 被观察者
abstract class Subject {
    private list: IObserver[] = [];

    addObserver(ob: IObserver) {
        this.list.push(ob);
    }

    removeObserver(ob: IObserver) {
        let index = this.list.indexOf(ob);
        if (index > -1) {
            this.list.splice(index, 1);
        }
    }

    notifyObserver() {
        for (const iterator of this.list) {
            iterator.update && iterator.update();
        }
    }
}

// 具体被观察者
class ConcreteSubject extends Subject {
    sendTask() {
        console.log("公司有个项目,完成可获得无限火力卡奖励");
        this.notifyObserver();
    }
}

// 观察者接口
interface IObserver {
    update(): void;
}

// 具体观察者A
class ObserverA implements IObserver {
    update() {
        console.log("ObserverA: 冲啊,卷死他们");
    }
}

// 具体观察者B
class ObserverB implements IObserver {
    update() {
        console.log("ObserverB: 我已经有十张了,把机会让给别人吧");
    }
}

// 具体观察者C
class ObserverC implements IObserver {
    update() {
        console.log("ObserverC: 我再集一张就能召唤神龙了");
    }
}

class Main {
    constructor() {
        let subject = new ConcreteSubject();
        let C = new ObserverC();
        subject.addObserver(new ObserverA());
        subject.addObserver(new ObserverB());
        subject.addObserver(C);
        
        // 你就别凑热闹了
        // subject.removeObserver(C);
        
        subject.sendTask();
    }
}
new Main();
观察者模式优缺点

1. 优点

  • 两者是抽象耦合的,降低了被观察者与观察者之间的耦合关系

  • 建立了一套触发机制,形成一条触发链 2. 缺点

  • 链式触发,在观察者多、链条长的情况下,效率会很低

  • 被观察者与观察者有循环依赖的情况下,出现循环调用

与发布订阅模式有什么不同

同学A:喂,我有个问题,这个观察者模式和发布订阅模式有什么不同?我怎么感觉一毛一样好嘛。

我:问得好,度娘同学你来回答一下这个问题。

发布订阅模式UML类图

WX20220502-154611@2x.png

发布订阅模式对比

观察者模式和发布订阅模式本质上看起来是一样的,都是实现观察通知(订阅发布)的过程。从类图可以看出发布订阅模式多了一个调度中心,发布者和调度者不知道彼此的存在,通过调度中心统一处理,将发布者的消息通知到其订阅者。

  • 发布订阅模式是一种多对多的关系
  • 观察者模式是一对多的关系
  • 发布订阅模式可以理解成,顾客(订阅者) - 淘宝(调度中心) - 商家(发布者)
  • 观察者模式可以理解成,用户(观察者) - 微信公众号(被观察者)
发布订阅模式demo
interface Publisher {
    topic: string;
    data: any;
}

interface Subscriber {
    param: { topic: string, callback: (data?: any) => void }[]
}

interface Channel {
    publish: (topic: string, data: any) => void;
    subscribe: (topic: string, callback: (data?: any) => void) => void;
    unsubscribe: (topic: string, callback: (data?: any) => void) => void;
}

class ConcreteChannel implements Channel {
    private subjects: { [key: string]: ((data?: any) => void)[] } = {};

    public subscribe(topic: string, callback: (data?: any) => void): void {
        if (!this.subjects[topic]) {
            this.subjects[topic] = [];
        }
        this.subjects[topic].push(callback);
    };

    public unsubscribe(topic: string, callback: (data?: any) => void): void {
        let index = this.subjects[topic].indexOf(callback);
        if (index > -1) {
            this.subjects[topic].splice(index, 1);
        }
    };

    public publish(topic: string, data: any): void {
        let itemA = this.subjects[topic];
        console.log(data);
        if (itemA) {
            itemA.forEach(item => item(data));
        }
    };
}

class ConcretePublisher implements Publisher {
    topic: string = "";
    data: any;
    constructor(topic: string, data: any) {
        this.topic = topic;
        this.data = data;
    }
}

class ConcreteSubscriber implements Subscriber {
    param: { topic: string, callback: (data?: any) => void }[]
    constructor(param: { topic: string, callback: (data?: any) => void }[]) {
        this.param = param;
    }
}

class Main {
    constructor() {
        let userA = new ConcreteSubscriber([{
            topic: "shop1",
            callback: () => {
                console.log("userA:等一波shop1开门")
            }
        }]);
        let userB = new ConcreteSubscriber([{
            topic: "shop1",
            callback: () => {
                console.log("userB:等一波shop1开门")
            }
        }, {
            topic: "shop2",
            callback: () => {
                console.log("userB:再等一波shop2开门")
            }
        }]);
        let userC = new ConcreteSubscriber([{
            topic: "shop2",
            callback: () => {
                console.log("userC:等一波shop2开门")
            }
        }]);

        let shop1 = new ConcretePublisher("shop1", "shop1开门了");
        let shop2 = new ConcretePublisher("shop2", "shop2开门了");

        let channel = new ConcreteChannel();
        userA.param.forEach(item => {
            channel.subscribe(item.topic, item.callback);
        })
        userB.param.forEach(item => {
            channel.subscribe(item.topic, item.callback);
        })
        userC.param.forEach(item => {
            channel.subscribe(item.topic, item.callback);
        })
        channel.publish(shop1.topic, shop1.data);
        // channel.unsubscribe(userB.param[1].topic,userB.param[1].callback);
        channel.publish(shop2.topic, shop2.data);
    }
}
new Main()
发布订阅模式优缺点

1. 优点

  • 时间解耦、发布者与订阅者双向解耦

  • 易与扩展 2. 缺点

  • 发布者无法获得订阅者的执行情况

同学B:啊这,这不就是事件调度管理嘛!

我:(偷偷看了Laya的EventDispatcher)你说的没错,on对应subscribe,emit对应publish,off对应unsubscribe,再加些所需的once,hasListener等等就是Laya引擎里的EventDispatcher了。

同学A:打断一下,如果我在事件未注册时,先发送了一条消息,那不就没了嘛,破玩意儿

我:(???又是这货,我怎么知道)那你先存个cacheList,等注册的时候你再取出来给人发送过去就好了。好了,今天的分享到此结束!!!(好险,还好我收的快)

思考与讨论

Mobx是通过函数响应式编程使状态管理变得简单和可扩展的状态管理库。

WX20220501-163426@2x.png

  • 被观察者 State(存储state的store)
  • 观察者 使用了Derivation的组件(Computed values、Reactions) Actions是所有会修改状态的代码,在修改了应用状态(被观察者)之后,所有的观察者都会同步执行副作用。

StateStore

// stateStore
import { observable, action, computed } from 'mobx'

class AppStore {
    levelNameMap = ["", "青铜", "白银", "黄金"];

    @observable achievementInfo = {
        level: 1,
        name: this.levelNameMap[1]
    }
    @computed get achievementName() {
        return this.achievementInfo.name;
    }

    @action achievementLevelUp() {
        let level = Math.min(this.achievementInfo.level + 1, this.levelNameMap.length - 1);
        this.achievementInfo = {
            level,
            name: this.levelNameMap[level]
        }
    }
}

export default new AppStore();

接下来只要在组件中使用这些状态数据,并将组件设置为observer(观察者),然后在某些地方触发Action,组件就会自动响应。

import { Component } from 'react'
import { Button, View } from '@tarojs/components'
import './index.scss'
import { inject, observer } from 'mobx-react'

@inject("appStore")
@observer
export default class Index extends Component<IProps> {
  render() {
    return (
      <View className='index-page'>
        <View>{this.props.appStore.achievementName}</View>
        <Button onClick={this.levelUp}>升级</Button>
      </View>
    )
  }

  levelUp = () => {
    this.props.appStore.achievementLevelUp();
  }
}

2022-05-02 14.17.54.gif

猜一猜MobX是观察者模式?发布订阅模式?

有人认为Mobx在广义上可以理解成(强行理解)是一个观察者模式。

(实际上作者认为:🤙🤙🏼🤙🏾Guise, #mobx isn't pubsub, or your grandpa's observer pattern. Nay, it is a carefully orchestrated observable dimensional portal fueled by the power cosmic. It doesn't do change detection, it's actually a level 20 psionic with soul knife, slashing your viewmodel into submission.)