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

Open
wolfdu opened this issue Jan 25, 2018 · 0 comments
Open

设计模式之观察者模式 #23

wolfdu opened this issue Jan 25, 2018 · 0 comments

Comments

@wolfdu
Copy link
Owner

wolfdu commented Jan 25, 2018

https://wolfdu.fun/post?postId=5a69f2c9b890b156d0fae647

最近在了解MV*的过程中,了解到其中很多的实现与观察者模式有密切关系。
观察者模式从字面上就容易理解,我也自认为如此,但是为了了解其准确的定义就去Google了下,发现很多博文下对观察者模式发布/订阅模式描述不一,有些认为这两种模式是一样的,而另外一部分认为这两种模式是有区别。

为了探其究竟整理一下相关学习过程

《JavaScript设计模式》中的观察者模式

在众多文章中很多都是参考了《JavaScript设计模式》这本书,所以我决定先从书中下手。

在书中的15章-观察者模式开篇是这样描述的:

在事件驱动的环境中,比如浏览器这种持续寻求用户关注的环境中,观察者模式(又名发布者-订阅者(publisher-subscriber)模式)是一中管理人与其任务之间的的关系(确切的讲,是对象及其行为和状态之间的关系)的得力工具。

看到这里我也会认为这两种模式应该就是同样一种模式,只是叫法不同罢了。但是有了之前的疑问所以我决定先看完整章再思考是否这么认为。

简单了解观察者模式中的两种角色:观察者和被观察者,或者叫做发布者和订阅者。

通过书中的示例体现两种角色以及他们的工作方式:

报纸的投送:
在报纸行业中有一些关键性的角色,首先是读者-订阅者(subscriber),另一个角色是报社-发布者(publisher)
确定了身份后分析一下各个角色的行为:

  • 订阅者:报纸来的时候我们会收到通知。我们会消费数据,并根据数据做出响应的反应。也就是订阅者会从发布者接收数据
  • 发布者:对应的发布者需要发送数据,也就是投送报纸(deliver)。一般来说发布者会有很多个订阅者,对应的一个订阅者也会订阅很多个发布者。

通过上述的场景描述这是一种多对多的关系,我们需要订阅者能够彼此独立的发生改变,发布者能接受由任何意向的订阅者。

我们不妨先用关系图来理清楚一家报社和订阅者间的关系:
报社和订阅者关系

通过上面的关系图我们大概可以得到这样的关系:

/**
 * 报纸供应商
 */
var NewYorkTimes = new Publisher()
var PeopleDaily = new Publisher()

/**
 * 订阅者订阅喜欢的报纸
 */
wolfdu
.subscribe(PeopleDaily)

Tom
.subscribe(NewYorkTimes)
.subscribe(PeopleDaily)

/**
 * 报社投递报纸
 */
NewYorkTimes
.deliver('Big apple')

PeopleDaily
.deliver('PeopleDaily-军事报')
.deliver('PeopleDaily-文史报')

发布者

我们根据这个关系来一步一步实现,先来看报社,每个报社应该有自己的订阅者,在有新报纸后他们会将报纸投递给每一个订阅者,投递完之后订阅者如何处理报纸就是订阅者的事情了。

/**
 * 发布者构造函数
 */
function Publisher() {
    this.subscribers = []
}

/**
 * 发布者投递报纸
 */
Publisher.prototype.deliver = function(data) {
    this.subscribers.forEach(function(subscriber) {
        subscriber.handlePaper(data) // 订阅者处理自己的报纸
    })
    return this
}

订阅者

然后来实现订阅者,每个订阅者有自己的名字和处理报纸的方法,同时他还应该有订阅报纸和退订报纸的功能。

/**
 * 订阅者构造函数
 */
function Subscriber(name, fn) {
    this.name = name
    this.handlePaper = fn
}

/**
 * 订阅方法
 */
Subscriber.prototype.subscribe = function(publisher) {
    var alreadyExists = publisher.subscribers.some((el) => el === this)
    if(!alreadyExists) {
        publisher.subscribers.push(this)
    }
    return this
}

/**
 * 取消订阅
 */
Subscriber.prototype.unsubscribe = function(publisher) {
    publisher.subscribers = publisher.subscribers.filter((el) => el !== this)
    return this
}

接下来我们看看是否实现我们预想的效果:

/**
 * 报纸供应商
 */
var NewYorkTimes = new Publisher()
var PeopleDaily = new Publisher()

/**
 * 订阅者声明
 */
var wolfdu = new Subscriber('wolfdu', function(from) {
    console.log(`Delivery from ${from} to ${this.name}`)
})

var Tom = new Subscriber('Tom', function(from) {
    console.log(`Delivery from ${from} to ${this.name}`)
})

/**
 * 报社投递报纸
 */
NewYorkTimes
.deliver('Big apple')

PeopleDaily
.deliver('PeopleDaily-军事报')
.deliver('PeopleDaily-文史报')

看看我们的执行结果:

Delivery from Big apple to Tom
Delivery from PeopleDaily-军事报 to wolfdu
Delivery from PeopleDaily-军事报 to Tom
Delivery from PeopleDaily-文史报 to wolfdu
Delivery from PeopleDaily-文史报 to Tom

到这里我们基本能够了解到书中所描述的观察者模式的大致实现了,那么这究竟是不是观察者模式的实现呢?
维基百科-Observer pattern的描述中我们了解到:

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

我们可以这么理解:观察者模式 在软件设计中是一个对象(主体/发布者),维护一个依赖列表(观察者/订阅者),当任何状态发生改变自动通知它们。

observer-pattern

这里描述的观察者模式和书中给出的示例似乎是一致的,那么接下来我们来看一看发布/订阅模式是怎么一回事呢?

发布/订阅模式

通过Google我发现其实在软件设计中有一种消息模式叫做Publish–subscribe pattern也就是我们所说的发布/订阅模式,这种模式和我们的观察这模式确实很相似。

我们看看维基百科-Publish–subscribe pattern的解释:

In software architecture, publish–subscribe is a messaging pattern where senders of messages, called publishers, do not program the messages to be sent directly to specific receivers, called subscribers, but instead categorize published messages into classes without knowledge of which subscribers, if any, there may be.

我是这么理解的,这种模式中也存在消息发布者和消息的订阅者,但是发布者不会将消息直接发送给订阅者而是将消息分类放到一个中间件中(发布订阅中心),并不知道有那些订阅者。

用一个简单的关系图表示:
pub-sub

这里我们也简单实现一个发布/订阅模式的示例:

/**
 * 订阅发布中心
 */
var publishSubscribeCenter = {}

;(function(obj) {
    var topics = {}
    var subUid = -1

    /**
     * 发布
     */
    obj.publish = function(topic, args) {
        if (!topics[topic]) {
            return false
        }
        var subscribers = topics[topic]
        var len = subscribers ? subscribers.length : 0
        while (len--) {
            subscribers[len].func(topic, args)
        }
    }

    /**
     * 订阅
     */
    obj.subscribe = function (topic, func) {
        if (!topics[topic]) {
            topics[topic] = []
        }
        var token = (++subUid).toString()
        topics[topic].push({token, func})
        return token
    }

    /**
     * 取消订阅
     */
    obj.unsubscribe = function (token) {
        for (var m in topics) {
            if (topics[m]) {
                topics[m].filter((el) => el.token !== token)
                return token
            }
        }
        return this
    }
}(publishSubscribeCenter))

// 订阅一个PeopleDaily
publishSubscribeCenter.subscribe('PeopleDaily', function(topic, data) {
    console.log(`${topic} : ${data}`)
})

// 发布内容
publishSubscribeCenter.publish('PeopleDaily', '军事报')

总结

通过查阅资料和实现对应的模式示例后,在一开始对这两种设计模式的疑惑也慢慢清晰了。

最让人迷惑的地方应该就是这两种模式下都有两个相同的概念发布者和订阅者。现在看来相同点也只有这一点了。

**观察者模式:**其中的的角色为观察者(observer)和主题(subject)对象,observer需要观察subject时,需先到subject里面进行注册(subject对象持有observer对象的集合),然后,当subject对象的内部状态发生变化时,把这个变化通知所有的观察者(其中的观察者/主题(observer/subject)对应的就是订阅者/发布者(subscriber/publisher))。

发布/订阅模式其中的角色为发布者(publisher)和订阅者(subscriber),pub和sub之间没有直接的耦合关系,消息事件的发布和订阅都是由消息中间件进行处理。

通过一张图来展示其中的区别:
diff-observer-pubsub

那么到这里就解决了最开始的疑惑了,这两中模式并非同一种模式,其实对比起来看差异还是比较明显的,希望对你也有帮助 (•‾̑⌣‾̑•)✧˖°

参考文章:
Observer vs Pub-Sub
观察者模式 vs 发布-订阅模式

若文中有知识整理错误或遗漏的地方请务必指出,非常感谢。如果对你有一丢丢帮助或引起你的思考,可以点赞鼓励一下作者=^_^=

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

1 participant