写点什么

『设计模式』80 年代的人们竟然领悟了设计模式 -- 发布者 / 订阅者模式 (包括发布者 / 订阅者模式和观察者模式的区别)

用户头像
风骨散人
关注
发布于: 2021 年 03 月 23 日
『设计模式』80年代的人们竟然领悟了设计模式-- 发布者/订阅者模式 (包括发布者/订阅者模式和观察者模式的区别)

发布-订阅模式

在软件架构中,发布订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。

举个报纸的例子:

还是得说一下报纸,有人说报纸不就是观察者模式,那得有多少观察者和主题?一张报纸那么多板块,订报纸的人那么多,难道要一个人一个人的通知,显然不现实。如果在记者(编辑)和读者之间加了一个载体报纸,那么这还是观察者模式吗?

无数的编辑将新闻发到报设,报社在将信息整合到报纸同意发送到读者手中,显然这不是观察者模式,观察者模式中,观察者和主题有着很强的耦合性,而在这里显然记者不认识读者,读者也不能通过报纸直接和编辑通信,这就是发布者订阅者模式,简单来说和发布者的区别就是多了一家报社。兴许我这朴实的例子并不能让你看明白,我们看一下国外的大佬怎么说?


观察者模式和发布订阅模式有什么区别?

之前我的回答是《Head First 设计模式》里讲的:Publishers + Subscribers = Observer Pattern 而且 GoF 也说只是别称。但是众说纷纭,可能看问题的观点不同,前人是大佬,后人也要用发展性的眼观看待,我么就来看看这两种设计模式到底有什么区别。

首先我们来重新来回顾一下观察者模式:

观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。而观察者模式属于行为型模式,行为型模式关注的是对象之间的通讯,观察者模式就是观察者和被观察者之间的通讯。

由上图可以详细的看出观察者和被观察者是密切联系的。

我们再来看看发布者-订阅者模式


在“发布者-订阅者”模式中,称为发布者的消息发送者不会将消息编程为直接发送给称为订阅者的特定接收者。这意味着发布者和订阅者不知道彼此的存在。存在第三个组件,称为代理或消息代理或事件总线,它由发布者和订阅者都知道,它过滤所有传入的消息并相应地分发它们。换句话说,pub-sub 是用于在不同系统组件之间传递消息的模式,而这些组件不知道关于彼此身份的任何信息。经纪人如何过滤所有消息?实际上,有几个消息过滤过程。最常用的方法有:基于主题和基于内容的。


我们放几张图,方便理解:


总结出的差异


  • 观察者模式中,观察者知道主题,*主题*也维护观察者的记录。而在发布者/订阅者中,*发布者和订阅者*不需要彼此了解。他们只是在消息队列或代理的帮助下进行通信。

  • 在发布者/订阅者模式中,与观察者模式相反,组件是松散耦合的。

  • 观察者模式大多数是以同步方式实现的,即,当某个事件发生时,主题调用其所有观察者的适当方法。的发行者/订户图案在一个实施大多异步方式(使用消息队列)。

  • 观察者模式需要在单个应用程序地址空间中实现。另一方面,发布者/订阅者模式更多地是跨应用程序模式。


尽管这些模式之间存在差异,但有些人可能会说 Publisher-Subscriber 模式是 Observer 模式的变体,因为它们之间在概念上相似。而且这根本没有错。无需认真对待差异。它们是相似的,不是吗?


注: 上文参考地址:https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c


优点:

  • 松耦合

发布者与订阅者松耦合,甚至不需要知道它们的存在。由于主题才是关注的焦点,发布者和订阅者可以对系统拓扑结构保持一无所知。各自继续正常操作而无需顾及对方。

  • 可扩展性

通过并行操作,消息缓存,基于树或基于网络的路由等技术,发布/订阅提供了比传统的客户端–服务器更好的可扩展性。

缺点:

  • 发布/订阅系统最严重的问题是其主要优点的副作用:发布者解耦订阅者。

  • 消息交付问题:发布/订阅系统必须仔细设计,才能提供特定的应用程序可能需要的更强大的系统性能,因为松耦合,无论订阅者是否正常收到发布内容,订阅器都会停止发送。

  • 订阅器中的内容随着发布者使用者的增加服务器的负载,对中介服务器是极大的考验!

UML 图

具体实现

别诟病我的中文写代码,为了看的更清楚一点,因为不好理解,我看了好久的!


1.发布者接口


package 发布者订阅者模式;
public interface I发布者接口<M> { public void publish(订阅器 subscribePublish, M message, boolean isInstantMsg); //使用哪个订阅器,发布什么信息}
复制代码
2.订阅者者接口


package 发布者订阅者模式;
public interface I订阅者接口<M> { public void subcribe(订阅器 subscribePublish); //从哪个订阅器订阅 public void unSubcribe(订阅器 subscribePublish); //取消订阅 public void update(String publisher, M message); //更新操作,参考观察者模式}
复制代码


3.实际发布者 1


package 发布者订阅者模式; public class Ac实际发布者<M> implements I发布者接口<M> {    private String name;     public Ac实际发布者(String name) {        super();        this.name = name;    }     public void publish(订阅器 subscribePublish, M message, boolean isInstantMsg) {        subscribePublish.publish(this.name, message, isInstantMsg);    }}
复制代码


4.实际订阅者 1


 package 发布者订阅者模式; public class Ac实际订阅者<M> implements I订阅者接口<M> {    public String name;     public Ac实际订阅者(String name) {        super();        this.name = name;    }     public void subcribe(订阅器 subscribePublish) {        subscribePublish.subcribe(this);    }     public void unSubcribe(订阅器 subscribePublish) {        subscribePublish.unSubcribe(this);    }     public void update(String publisher, M message) {        System.out.println(this.name + "收到" + publisher + "发来的消息:" + message.toString());    }}
复制代码


5.订阅信息(报纸?RSS?微信公众号?都是)


package 发布者订阅者模式;
public class 发布的信息<M> { private String publisher; private M m;
public 发布的信息(String publisher, M m) { this.publisher = publisher; this.m = m; }
public String getPublisher() { return publisher; }
public void setPublisher(String publisher) { this.publisher = publisher; }
public M getMsg() { return m; }
public void setMsg(M m) { this.m = m; }}
复制代码


###### 6.订阅器


package 发布者订阅者模式; import java.util.ArrayList;import java.util.List;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.BlockingQueue; public class 订阅器<M> {    //订阅器名称    private String name;    //订阅器队列容量    final int QUEUE_CAPACITY = 300; //最大订阅器数量    //订阅器存储队列    private BlockingQueue<发布的信息> queue = new ArrayBlockingQueue<发布的信息>(QUEUE_CAPACITY);    //订阅者    private List<I订阅者接口> subcribers = new ArrayList<I订阅者接口>();     public 订阅器(String name) {        this.name = name;    }     public void publish(String publisher, M message, boolean isInstantMsg) {        if (isInstantMsg) {            update(publisher, message);            return;        }        发布的信息<M> m = new 发布的信息<M>(publisher, message);        if (!queue.offer(m)) {            update();        }    }     public void subcribe(I订阅者接口 subcriber) {        subcribers.add(subcriber);    }     public void unSubcribe(I订阅者接口 subcriber) {        subcribers.remove(subcriber);    }     public void update() {        发布的信息 m = null;        while ((m = queue.peek()) != null) {            this.update(m.getPublisher(), (M) m.getMsg());        }    }     public void update(String publisher, M Msg) {        for (I订阅者接口 subcriber : subcribers) {            subcriber.update(publisher, Msg);        }    }}  
复制代码
7.测试


package 发布者订阅者模式; public class MainTest {    public static void main(String[] args) {        	订阅器<String> subscribePublish = new 订阅器<String>("报纸订阅平台");        I发布者接口<String> publisher1 = new Ac实际发布者<String>("纽约时报");        I订阅者接口<String> subcriber1 = new Ac实际订阅者<String>("特朗普");        I订阅者接口<String> subcriber2 = new Ac实际订阅者<String>("普京");        subcriber1.subcribe(subscribePublish);        subcriber2.subcribe(subscribePublish);        publisher1.publish(subscribePublish, "美国新型冠状病毒爆发的原因", true);        publisher1.publish(subscribePublish, "竟然是", true);        publisher1.publish(subscribePublish, "川普不作为", false);    }}
复制代码


写在最后:

我叫风骨散人,名字的意思是我多想可以不低头的自由生活,可现实却不是这样。家境贫寒,总得向这个世界低头,所以我一直在奋斗,想改变我的命运给亲人好的生活,希望同样被生活绑架的你可以通过自己的努力改变现状,深知成年人的世界里没有容易二字。目前是一名在校大学生,预计考研,热爱编程,热爱技术,喜欢分享,知识无界,希望我的分享可以帮到你!

如果有什么想看的,可以私信我,如果在能力范围内,我会发布相应的博文!

>感谢大家的阅读!😘你的点赞、收藏、关注是对我最大的鼓励!


发布于: 2021 年 03 月 23 日阅读数: 13
用户头像

风骨散人

关注

还未添加个人签名 2021.03.22 加入

大学僧,ACM退役选手。 名字的含义:希望可以通过努力,能力让家人拥有富足的生活而不是为了生计而到处奔波。“世人慌慌张张,不过是图碎银几两。偏偏这碎银几两,能解世间惆怅,可让父母安康,可护幼子成长 …”

评论

发布
暂无评论
『设计模式』80年代的人们竟然领悟了设计模式-- 发布者/订阅者模式 (包括发布者/订阅者模式和观察者模式的区别)