Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第 23 题:介绍下观察者模式和订阅-发布模式的区别,各自适用于什么场景 #25

Open
Spades-S opened this issue Feb 28, 2019 · 39 comments

Comments

@Spades-S
Copy link

观察者模式中主体和观察者是互相感知的,发布-订阅模式是借助第三方来实现调度的,发布者和订阅者之间互不感知
image

@fingerpan
Copy link

fingerpan commented Mar 1, 2019

  1. 发布-订阅模式就好像报社, 邮局和个人的关系,报纸的订阅和分发是由邮局来完成的。报社只负责将报纸发送给邮局。
  2. 观察者模式就好像 个体奶农和个人的关系。奶农负责统计有多少人订了产品,所以个人都会有一个相同拿牛奶的方法。奶农有新奶了就负责调用这个方法。

@1042478910
Copy link

可不可以理解 为 观察者模式没中间商赚差价
发布订阅模式 有中间商赚差价

@GitHdu
Copy link

GitHdu commented Mar 2, 2019

观察者模式依赖一旦改变就会触发更新,而订阅发布模式则需要手动触发更新

@alanchanzm
Copy link

联系

发布-订阅模式是观察者模式的一种变体。发布-订阅只是把一部分功能抽象成一个独立的ChangeManager。

意图

都是某个对象(subject, publisher)改变,使依赖于它的多个对象(observers, subscribers)得到通知。

区别与适用场景

总的来说,发布-订阅模式适合更复杂的场景。

在「一对多」的场景下,发布者的某次更新只想通知它的部分订阅者?

在「多对一」或者「多对多」场景下。一个订阅者依赖于多个发布者,某个发布者更新后是否需要通知订阅者?还是等所有发布者都更新完毕再通知订阅者?

这些逻辑都可以放到ChangeManager里。

@fingerpan
Copy link

vue 中的 observer watcher dep 可以理解为发布订阅者模式吧?

应该是观察者模式。 vue的事件通讯机制才是发布订阅模式

@rocky-191
Copy link

vue 中的 observer watcher dep 可以理解为发布订阅者模式吧?

应该是观察者模式。 vue的事件通讯机制才是发布订阅模式

数据劫持+发布订阅

@fingerpan
Copy link

vue 中的 observer watcher dep 可以理解为发布订阅者模式吧?

应该是观察者模式。 vue的事件通讯机制才是发布订阅模式

数据劫持+发布订阅

从命名上就可以区别,观察者(observer)和主题(subject)对象

@qiuguixin
Copy link

观察者模式为一刀切模式,对所有订阅者一视同仁
发布订阅模式 可以戴有色眼镜,有一层过滤或者说暗箱操作😄

@vian94
Copy link

vian94 commented May 12, 2019

https://juejin.im/post/5cd81a20e51d453b4558d858

@halionn
Copy link

halionn commented Jun 18, 2019

发布-订阅增加了一个中介者,发布者和订阅着只和中介者打交到。中介者持有发布者和订阅者都引用或传入的回调,用来做订阅和通知。

@xxxin128
Copy link

观察者模式由具体目标调度,每个被订阅的目标里面都需要有对观察者的处理,会造成代码的冗余。而发布订阅模式则统一由调度中心处理,消除了发布者和订阅者之间的依赖。

@monw3c
Copy link

monw3c commented Jul 11, 2019

低耦合和完全解藕

@Hunterang
Copy link

多个发布订阅构成观察者模式,触发条件的话,感觉都是类似监听的方式,区别不明显

@PatrickLh
Copy link

PatrickLh commented Jul 11, 2019

参考了Java的部分实现,观察者模式代码如下:

var subject = {
	observers: [],
	notify() {
		this.observers.forEach(observer =>{
			observer.update()
		})
	},
	attach (observer) {
		this.observers.push(observer)
	}
}
var observer = {
	update(){
		alert('updated')
	}
}
subject.attach(observer)
subject.notify()

而使用订阅发布模式,使用中间订阅发布对象的方式如下

var publisher = {
	publish(pubsub) {
		pubsub.publish()
	}
}
var pubsub = {
	subscribes: [],
	publish() {
		this.subscribes.forEach(subscribe =>{
			subscribe.update();
		})
	},
	subscribe(sub) {
		this.subscribes.push(sub)
	}
}
var subscribe = {
	update() {
		console.log('update')
	},
        subscribe(pubsub) {
            pubsub.subscribe(this);
        }
}
subscribe.subscribe(pubsub)
publisher.publish(pubsub)

自己认为,两种模式本质都是一样的,主要关键点都在于注册(添加到注册数组中)和触发(触发注册数组中的内容),只是订阅/发布模式对注册和触发进行了解耦。可以看到,使用订阅发布模式中发布者触发publish的时候,可以选择触发哪一些订阅者集合(因为publish参数传递了中间集合,可以定义多个pubsub集合),而观察者模式则只能触发所有的被观察对象。

@xufeiayang
Copy link

vue 中的 observer watcher dep 可以理解为发布订阅者模式吧?

应该是观察者模式。 vue的事件通讯机制才是发布订阅模式

vue双向绑定过程涉及发布订阅和观察者,setter是观察者,更新的过程是发布订阅

@Yxiuchao
Copy link

应该是发布订阅模式吧,订阅发布模式说的应该和观察者模式是同一个模式的两种叫法吧

@zhishaofei3
Copy link

参考了Java的部分实现,观察者模式代码如下:

var subject = {
	observers: [],
	notify() {
		this.observers.forEach(observer =>{
			observer.update()
		})
	},
	attach (observer) {
		this.observers.push(observer)
	}
}
var observer = {
	update(){
		alert('updated')
	}
}
subject.attach(observer)
subject.notify()

而使用订阅发布模式,使用中间订阅发布对象的方式如下

var publisher = {
	publish(pubsub) {
		pubsub.publish()
	}
}
var pubsub = {
	subscribes: [],
	publish() {
		this.subscribes.forEach(subscribe =>{
			subscribe.update();
		})
	},
	subscribe(sub) {
		this.subscribes.push(sub)
	}
}
var subscribe = {
	update() {
		console.log('update')
	},
        subscribe(pubsub) {
            pubsub.subscribe(this);
        }
}
subscribe.subscribe(pubsub)
publisher.publish(pubsub)

自己认为,两种模式本质都是一样的,主要关键点都在于注册(添加到注册数组中)和触发(触发注册数组中的内容),只是订阅/发布模式对注册和触发进行了解耦。可以看到,使用订阅发布模式中发布者触发publish的时候,可以选择触发哪一些订阅者集合(因为publish参数传递了中间集合,可以定义多个pubsub集合),而观察者模式则只能触发所有的被观察对象。

还是看代码清楚

@zacard-orc
Copy link

可不可以理解 为 观察者模式没中间商赚差价
发布订阅模式 有中间商赚差价

确定是赚了差价吗,应该算是亏本买卖吧,毕竟有投入的

@caihaihong
Copy link

文档:观察者模式.md
链接:http://note.youdao.com/noteshare?id=c2f89f7d0076a5aeb9f55e5d8c107eb8&sub=58B92DCB4EC34D8D93E56F05AB9DD698
文档:发布订阅模式.md
链接:http://note.youdao.com/noteshare?id=223c881cd073f84e571a5298767fd45a&sub=DBAAF3FFC5A64BE58CC91C24249ADA23

@daiyunchao
Copy link

观察者模式就像 mvvm ,m的变化v可以感知而对应的做出变化(不是很准确,因为观察者是单向的)
而订阅发布模式就像mvc m的变化必须依靠c来控制通知view model有变化,view才做出相应

@atJiangBei
Copy link

我的回答:观察者模式: 假如现在有一个楼盘将要开盘(这个楼盘是一个主体,里面有销售人员,有保安人员等等), 然后有很多人(很多观察者)来买房,然后这些人把自己的电话留在售楼部(每个观察者都把自己注册到目标), 当确定楼盘开盘日期的时候,就由售楼部的销售人员通知这些留下电话的买房的人。 在这里,买房的人必须把自己的电话号码留在售楼部(观察者必须把自己注册到主体里), 不然楼盘的销售人员是无法通知要买房的人的,这之间就产生了联系。 发布订阅模式: 假设有一个中介,有很多想把自己的房子卖了的人,就把自己的房产信息留下。 然后有很多想要买二手房的人,把自己的电话也留在了中介。 这之间,买房的和卖房的一点关系都没有,谁都不知道谁的存在,买房的等着中介通知它有没有房。

@zhangyu0218
Copy link

应用到react上 ,观察者模式是不是就像父子组件传值 而发布订阅模式就像是组件之间信息通过redux传递呢。

@Longgererer
Copy link

观察者模式和发布订阅模式都有观察者和发布这两个对象

观察者模式没有中介,发布者和订阅者必须知道对方的存在

发布订阅模式有中介,发布者和订阅者不需要知道对方是谁,只要通过中介进行信息的传递和过滤就可以了

这是最大的不同

@bbrucechen
Copy link

观察者模式和发布-订阅模式的共同点在于都是发布者发布信息使得订阅者发起修改
差异在于发布-订阅者模式较观察者模式多了一个信息中介,这个信息中介其实就是一个过滤器,它集合了一些有着共同特点的订阅者,而且过滤器可以有多个
适用场景:
观察者模式更使用单一的事件订阅;
发布-订阅者更适用于发布者需要将触发同一事件的不同对象通知给相应的订阅者这样的场景。比如A触发修改事件时通过给a1,a2,a3订阅者,而B触发修改事件时通知给b1,b2,b3订阅者。这个时候我们就可以挺过两个信息中介,一个专门通知a1,a2,a3,另一个专门通知b1,b2,b3,而发布者只需要通知信息中介即可。

@xianengneng
Copy link

mvvm

mvvm 模式 m 和v 也需要vm 来赚中间差价,你这样理解观察者模式是不是有点问题呢?

@stevekeol
Copy link

观察者模式由具体目标调度,每个被订阅的目标里面都需要有对观察者的处理,会造成代码的冗余。而发布订阅模式则统一由调度中心处理,消除了发布者和订阅者之间的依赖。

对滴

@Kyo1992
Copy link

Kyo1992 commented Jun 1, 2020

观察者模式就像你在酒店直接给技师打电话让技师过来服务,发布订阅就像你先去xxx会所然后到了以后会所给你安排技术服务。。。不知道我理解得有没有问题

@JTangming
Copy link

JTangming commented Jun 11, 2020

观察者模式 vs 发布订阅模式

观察者模式

所谓观察者模式,其实就是为了实现松耦合(loosely coupled)

举个例子,当数据有更新,如 changed() 方法被调用,用来更新 state 数据,比如温度、气压等等。
这样写的问题是,如果想更新更多的信息,比如说湿度,那就要去修改 changed() 方法的代码,这就是紧耦合的坏处。

对于观察者模式,我们仅仅维护一个可观察者对象即可,即一个 Observable 实例,当有数据变更时,它只需维护一套观察者(Observer)的集合,这些 Observer 实现相同的接口,Subject 只需要知道,通知 Observer 时,需要调用哪个统一方法就好了。如下图:
observable

发布订阅模式

在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。

那他们之间如何交流?

答案是,通过第三者触发,也就是在消息队列里面,发布者和订阅者通过事件名来联系,匹配上后直接执行对应订阅者的方法即可。

如图:
pub-sub

与观察者模式不一样,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。

最后,附一张图总结一下:
summary

区别
观察者模式里,只有两个角色 —— 观察者 + 被观察者
而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个管理并执行消息队列的“经纪人Broker”

再者

观察者和被观察者,是松耦合的关系
发布者和订阅者,则完全不存在耦合

Reference

@CodingMeUp
Copy link

观察者模式由具体目标调度,每个被订阅的目标里面都需要有对观察者的处理,会造成代码的冗余。而发布订阅模式则统一由调度中心处理,消除了发布者和订阅者之间的依赖

@dancingTx
Copy link

我的理解是,举个例子:观察者模式相等于A在文章写好之后直接发送给B和C进行查看,也就是说A与B、C直接强关联,
而发布订阅模式 相当于 A在写好文章后,发布到第三方文章发布平台,而订阅了A文章的B和C会收到平台得推送,进而查看文章,
也就是说A与B、C直接属于间接弱关联。

@ppambler
Copy link

ppambler commented Aug 6, 2020

双向绑定的效果可以理解成观察者模式,单向数据流的效果可以理解成发布订阅模式

生活化一点就是:

发布者就是资源的生产者,而订阅者就是需要资源的人(也就是消费者),我们是线下面对面交易?还是网上交易?

@huangjihua
Copy link

举个例子:观察者模式好比用户从品牌厂家提车,厂家发货了用户能直接收到通知;
发布订阅者模式好比用户从4S店提车,这就需要4S店通知你是否可以来提车。

@Rabbitzzc
Copy link

在 JS 中,事件订阅应该属于发布-订阅模式。比如 EventBus,对注册的多种事件,可以单独触发,比如只触发 A 事件,则订阅的 B 事件并不会处理。

@Elgar17
Copy link

Elgar17 commented Dec 29, 2020

发布订阅模式

发布订阅模式叫观察者模式。

举一个生活中的例子,很多了订阅了一个博客,相当于触发了一个 on 事件(下面会介绍),当博客的发件人发布一篇博客时,会通知每一个订阅博客的人,相当于触发了 emit事件。

实际应用有 Vue 中的 $on$emit 方法。

(1)介绍

这样说可能难理解,简单的说,写一个构造函数,像这样:

function EventEmitter(){
}

现在我们要订阅一件事,就是给构造函数添加一个方法,像这样:

EventEmitter.prototype.on = function(name, cb){
}
// 参数1:函数名
// 参数2:要执行的函数

事件订阅好了,该发布了,这样:

EventEmitter.prototype.emit = function(name,...args){
}
// 参数1: 要触发的函数名
// 参数2: 需要给函数传入的参数

当时机到时,你通过 emit 传入一个函数名来执行自己定义好的函数。

如果我们以后不用一个事件,那么应该把他移除掉对吧。

EventEmitter.prototype.off = function(name){
}

骨架搭建好了,先模拟用一下,不着急实现,首先要知道怎么用对吧。

// 创建实例
var emitter = new EventEmitter();

// 订阅一个事件
emitter.on("sayHi", function(data){
    console.log(data)
})

// 发布一个消息
emitter.emit("sayHi","Hi") // 执行sayHi函数,应该打印 Hi

这个就是大概的流程,下面我们开始实现。

(2)实现

我们看到,在 on 里有一个回调函数,在emit 的时候执行,所以我们在构造函数中添加一个对象,保存这些函数。

function EventEmitter(){
    this.list = {}
}

现在可以实现 on 方法了,第一个参数函数名,第二个参数为函数。

EventEmitter.prototype.on = function (name, cb) {
    if (this.list[name]) {
        this.list[name].push(cb) // 追加方法
    } else {
        this.list[name] = [cb]; // 创建一个name事件
    }
}

我们先应该判断一下是否有这个方法,要不然会覆盖之前写的方法,如果存在我们追加这个函数即可。

不存在这个方法,我们以 name 为名字创建一个数组,把函数添加即可。

现在实现 emit 方法,当触发 emit 我们应该执行对应的函数。

EventEmitter.prototype.emit = function (name, ...args) {
    let _this = this;
    // 如果事件不存在返回 false
    if (!this.list[name]) return false;

    // 遍历 this.list[name] 中的每一个函数并执行
    this.list[name].forEach(fn => {
        fn.apply(this, args);
    })
    return true;
}

还有一个 off 方法,取消订阅。

EventEmitter.prototype.off = function (name, fn) {
    // 如果事件不存在返回 false
    if (!this.list[name]) return false;

    // 循环数组找到该函数并移除
    for (let i = 0; i < this.list[name].length; i++) {
        if (this.list[name][i] == fn) {
            this.list[name].splice(i, 1);
        }
    }
    return true
}

基本功能实现了,使用试一下:

var emitter = new EventEmitter();

// 用户1 添加订阅
function sayHi1(data) {
    console.log(data, "user1")
}
emitter.on("sayHi", sayHi1)

// 用户2 添加订阅
function sayHi2(data) {
    console.log(data, "user2")
}
emitter.on("sayHi", sayHi2)

// 取消 user1 的订阅
emitter.off("sayHi", sayHi1)

// 发布
emitter.emit("sayHi", "订阅者")

// 打印
// 订阅者 user2

用 class 语法重写了一下完整的。

class EventEmitter {
    constructor() {
        this.list = {}
    }
    on(name, cb) {
        if (this.list[name]) {
            this.list[name].push(cb) // 追加方法
        } else {
            this.list[name] = [cb]; // 创建一个name事件
        }
    }
    emit(name, ...args) {
        let _this = this;
        // 如果事件不存在返回 false
        if (!this.list[name]) return false;

        // 遍历 this.list[name] 中的每一个函数并执行
        this.list[name].forEach(fn => {
            fn.apply(_this, args);
        })
        return true;
    }
    off(name, fn) {
        // 如果事件不存在返回 false
        if (!this.list[name]) return false;

        // 循环数组找到该函数并移除
        for (let i = 0; i < this.list[name].length; i++) {
            if (this.list[name][i] == fn) {
                this.list[name].splice(i, 1);
            }
        }
        return true
    }
}

来源:js设计模式

@tornoda
Copy link

tornoda commented Jan 24, 2021

23三种设计模式中根本就没有上面说的发布—订阅模式。
看了一楼掘金的内容,总结如下:
发布订阅模式难道不是2个观察者模式的结合吗?中间那个又当爹又当妈。

@zhelingwang

This comment has been minimized.

@SnailOwO
Copy link

// 订阅模式
class eventEmitter1 {
        list = []

        on(event, fn) {
            (this.list[event] || (this.list[event] = [])).push(fn)
        }

        off(event, fn) {
            const curList = this.list[event] || []
            if (!curList.length) {
                return false
            }
            if (!fn) {
                curList.length = 0
            }
            for (const inx of Object.keys(curList)) {
                const val = curList[inx]
                if (val.fn === fn || val === fn) {
                    curList.splice(inx, 1)
                    break
                }
            }
        }

        once(event, fn) {
            const curList = this.list[event] || []

            function on() {
                fn.apply(this, arguments)
                on.fn = fn
                this.off(event, fn)
            }
            // on.fn = fn
            this.on(event, on)
        }

        emit() {
            const curList = this.list[Array.prototype.shift.call(arguments)]
            const params = [...arguments]
            if (!curList.length) {
                return false
            }
            console.log(curList);
            for (const val of curList) {
                val.apply(this, params)
            }
        }
    }

    function user1(content) {
        console.log('用户1订阅了:', content);
    }

    function user2(content) {
        console.log('用户2订阅了:', content);
    }

    function user3(content) {
        console.log('用户3订阅了:', content);
    }

    function user4(content) {
        console.log('用户4订阅了:', content);
    }


    const eventEmitter = new eventEmitter1()
        // 订阅
        // eventEmitter.on('article1', user1);
        // eventEmitter.on('article1', user2);
        // eventEmitter.on('article1', user3);

    // // 取消user2方法的订阅
    // eventEmitter.off('article1', user2);
    // eventEmitter.off('article1', null);

    eventEmitter.once('article2', user4)

    // 发布
    // eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
    // eventEmitter.emit('article1', 'Javascript 发布-订阅模式');
    eventEmitter.emit('article2', 'Javascript 观察者模式');
    eventEmitter.emit('article2', 'Javascript 观察者模式1');

@ufofacker
Copy link

观察者模式和发布订阅模式的设计思想是类似的,只是在应用场景上和实现策略上略有不同而已。
发布订阅模式可以认为是在观察者模式的基础上进行了一定的优化。通过增加消息代理管理器(大家常称其为:中间商,个人认为称其为代理商更合理些。因为,它的主要作用就是提供代理服务和消息处理加工的能力),优化了发布器和订阅器的耦合度,同时又对提供消息的发布器,及订阅器进行了隐藏。在发布订阅模式的实际使用的过程中,我们会发现对于订阅器而言,根本不知道是哪个发布器提供的消息服务。这为程序设计提供了更好的封装性,这也是其对比观察者模式的优势之一。
网上有个不错的的例子:定牛奶
观察者模式:人们到奶农那里预定牛奶,牛奶挤好了,奶农就会通知大家去取。
发布订阅模式:人们到奶站预定牛奶,人们不需要关心是哪个奶农提供的牛奶,奶农的奶挤好了也不需要关心买奶的人是谁。

@89466598946659
Copy link

89466598946659 commented Jun 2, 2022 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests