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

article/2025/10/27 1:19:55

一、概念

观察者(Observer),又称发布-订阅(Publish-Subscrice),属于23中设计模式之一。 发布订阅模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。

二、应用场景

以租房为例,小明到中介租房,但是合适的房源已经没有了,所以小明就会留下自己的联系方式给中介,一旦有合适的房源,中介就会通知小明。而中介对接向小明一样需求的客户有很多个,中介会通知他对接的所有顾客,这就是一个发布订阅的案例。其中小明就是订阅者,中介就是发布者,小明在中介里进行订阅之后,中介在找到符合房源后会进行发布,让订阅者收到消息。
类似这种就是发布订阅的案例,生活中的公众号、博客关注博主等等都是发布订阅的应用。

三、观察者模式和发布订阅模式的关系与区别

1、观察者设计模式:

观察者模式: 定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。
观察者模式的别名包括发布-订阅模式、模型-视图等。
细究的话,发布订阅和观察者有些不同,可以理解成发布订阅模式属于广义上的观察者模式。
发布-订阅设计模式

2、发布订阅模式

在发布-订阅模式,消息的发送方,叫做发布者(publishers),消息不会直接发送给特定的接收者,叫做订阅者。
意思就是发布者和订阅者不知道对方的存在。需要一个第三方组件,叫做订阅中心,它将订阅者和发布者串联起来,它过滤和分配所有输入的消息。换句话说,发布-订阅模式用来处理不同系统组件的信息交流,即使这些组件不知道对方的存在。

所以观察者模式和发布订阅者模式其实是大同小异,原理都是一样的,区别就在于有没有订阅中心。在GOF23中设计模式中是只有观察者模式,发布订阅模式是不存在的。有些人认为观察者和发布订阅是同一种模式,而有些人认为这是两种设计模式,关键看你怎么理解。
在这里插入图片描述

四、代码实现

1、有订阅中心

订阅者实体类Subscriber

public class Subscriber {private String name;public Subscriber(String name) {this.name = name;}public String getName() {return name;}
}

发布者实体类Publisher

public class Publisher {private String name;  //发布者的名字public Publisher(String name) {this.name = name;}public String getName() {return name;}
}

发布订阅中心SubPubCentral,发布订阅中心需要实现该接口

public interface SubPubCentral {/*** 添加发布者和订阅者* @param publisher* @param subscriber* @return*/public boolean subscribe(Publisher publisher, Subscriber subscriber);/*** 取消发布者和订阅者* @param publisher* @param subscriber* @return*/public boolean unsubscribe(Publisher publisher, Subscriber subscriber);/*** 发布消息* @param publisher* @param message*/public void publish(Publisher publisher, String message);
}

发布订阅中心实现类SubPubCentralImpl

public class SubPubCentralImpl implements SubPubCentral {private static Map<String, Set<String>> PubSubMap = new HashMap<>();  //存放所有的发布者的对应订阅者@Overridepublic boolean subscribe(Publisher publisher, Subscriber subscriber) {try {Set<String> subscriberSet = PubSubMap.get(publisher.getName()); //拿到当前发布者的所有订阅者if (subscriberSet == null)  //为空,之前不存在订阅者subscriberSet = new HashSet<>();boolean added = subscriberSet.add(subscriber.getName()); //添加订阅者if (added)  //添加订阅者成功。return PubSubMap.put(publisher.getName(), subscriberSet) != null;return false;  //订阅者添加失败或者该订阅之前则订阅了发布者} catch (Exception e) {e.printStackTrace();}return false;}@Overridepublic boolean unsubscribe(Publisher publisher, Subscriber subscriber) {try {Set<String> subscriberSet = PubSubMap.get(publisher.getName());if (subscriberSet == null)return false;boolean removed = subscriberSet.remove(subscriber.getName());       //删除取消订阅者if (removed)PubSubMap.put(publisher.getName(), subscriberSet); //更新订阅者列表return removed;} catch (Exception e) {e.printStackTrace();}return false;}@Overridepublic void publish(Publisher publisher, String message) {Set<String> subscriberSet = PubSubMap.get(publisher.getName());//拿到当前发布者的所有订阅者//遍历订阅者发送消息。 此处简单实现: 只需要打印出拿到的所有订阅者即可for (String subscriber : subscriberSet) {System.out.println(subscriber + "接收到" + publisher.getName() + "发布的新消息:" + message);}}
}

控制层,负责控制发布者的一系列行为

public class PublisherController {private SubPubCentral subPubCentral;  //订阅发布中心。public PublisherController(SubPubCentral subPubCentral) {this.subPubCentral = subPubCentral;}/*** 发布数据,假设前端传递的是一个id对象和一个内容对象。简单化,实际上会传递一个包装数据过来.** @param publisher* @param message*/public void publish(Publisher publisher, String message) {subPubCentral.publish(publisher, message);}
}

控制层,负责控制订阅者的一系列行为

public class SubscriberController {private SubPubCentral subPubCentral;public SubscriberController(SubPubCentral subPubCentral) {this.subPubCentral = subPubCentral;}/*** 订阅者与订阅发布者** @param publisher* @param subscriber*/public void subscribe(Publisher publisher, Subscriber subscriber) {subPubCentral.subscribe(publisher, subscriber);}public void unsubscribe(Publisher publisher, Subscriber subscriber) {subPubCentral.unsubscribe(publisher, subscriber);}
}

模拟发布订阅

public class Test {public static void main(String[] args) {//创建一个发布订阅中心SubPubCentral subPubCentral = new SubPubCentralImpl();//创建发布者和订阅者的控制层PublisherController publisherController = new PublisherController(subPubCentral);SubscriberController subscriberController = new SubscriberController(subPubCentral);//创建订阅者对象Subscriber subscriber1=new Subscriber("小明");Subscriber subscriber2=new Subscriber("小李");//创建发布者对象Publisher publisher=new Publisher("中介");//订阅者订阅subscriberController.subscribe(publisher, subscriber1);subscriberController.subscribe(publisher, subscriber2);//发布者发布新消息publisherController.publish(publisher, "天河区有新房出租!");System.out.println("\n----------------------------------------------------\n");//订阅者取消订阅subscriberController.unsubscribe(publisher, subscriber1);//发布者发布新消息publisherController.publish(publisher, "番禺区有新房出租!");}
}

执行结果
在这里插入图片描述

订阅者可以调用subscribe方法订阅对象和调用unsubscribe方法取消订阅对象,而发布者可以调用publish方法发布新消息给所有订阅他的对象。而不管是建立/取消订阅关系还是发布新消息都是有发布订阅中心PublisherController统一调用实现,就像现实中租房的中介,不管是租客还是房主,都是通过中介进行调度、通知。

2、观察者实现代码(无订阅中心)

主题Subject

public interface Subject {/*** 添加观察者** @param concreteObserver*/public void attach(ConcreteObserver concreteObserver);/*** 发布消息** @param msg*/public void notify(String msg);/*** 移除观察者** @param concreteObserver*/public void detach(ConcreteObserver concreteObserver);
}

观察者接口

public interface Observer {/*** 订阅内容* @param msg*/public void update(String subject, String msg);
}

主题实现类ConcreteSubject

public class ConcreteSubject implements Subject {//定义一个集合来储存观察者private ArrayList<ConcreteObserver> events = new ArrayList<>();//订阅的主题名字String name;public ConcreteSubject(String name) {this.name = name;}@Overridepublic void attach(ConcreteObserver concreteObserver) {events.add(concreteObserver);}@Overridepublic void notify(String msg) {for (int i = 0; i < events.size(); i++) {ConcreteObserver concreteObserver = events.get(i);concreteObserver.update(this.name, msg);}}@Overridepublic void detach(ConcreteObserver concreteObserver) {events.remove(concreteObserver);}
}

观察者实例

public class ConcreteObserver implements Observer {private String name;private Receiver receiver;public interface Receiver {void onMessage(String publisher, String msg);}//创建观察者public ConcreteObserver(String name) {this.name = name;}//创建观察者也可以加个回调函数,用来将消息调出去public ConcreteObserver(String name, Receiver receiver) {this.name = name;this.receiver = receiver;}@Overridepublic void update(String subject, String msg) {System.out.println(this.name + "收到" + subject + "发送的消息:" + msg);if (receiver != null) {receiver.onMessage(subject, msg);}}
}

模拟实现

public class Test {public static void main(String[] args) {//创建观察者ConcreteObserver concreteObserver1 = new ConcreteObserver("小明", new ConcreteObserver.Receiver() {@Overridepublic void onMessage(String publisher, String msg) {//收到消息System.out.println(publisher + "说" + msg);}});ConcreteObserver concreteObserver2 = new ConcreteObserver("小华");ConcreteObserver concreteObserver3 = new ConcreteObserver("小红");//创建主题ConcreteSubject concreteSubject = new ConcreteSubject("BOSS");concreteSubject.attach(concreteObserver1);concreteSubject.attach(concreteObserver2);concreteSubject.attach(concreteObserver3);concreteSubject.notify("今天放假!");}
}

运行结果
在这里插入图片描述


http://chatgpt.dhexx.cn/article/Zun1RLA3.shtml

相关文章

C++发布订阅模式

C发布订阅模式 发布订阅模式主要包含三个部分&#xff1a;消息发布、消息订阅者、消息处理中心。与观察者模式相比多出了消息处理中心模块&#xff0c;这样在结构上可以解耦订阅者与发布者&#xff0c;功能上更加的丰富。 观察者模式 结构设计 有一个消息list&#xff0c;主…

Java实现发布订阅模式

什么是发布订阅模式 发布订阅模式是软件开发者很常见的一种设计模式&#xff0c;很多开源库都使用了发布订阅模式&#xff0c;例如RxJava、EventBus、Vue等&#xff0c;所以学习该模式还是很有必要的。 该模式中存在一个或多个发布者&#xff0c;一个或多个订阅者&#xff0c…

设计模式 —— 发布订阅模式

设计模式 —— 发布订阅模式 《工欲善其事&#xff0c;必先利其器》 我在之前有写过一篇关于 《观察者模式》 的文章&#xff0c;大家有兴趣的可以去看看&#xff0c;个人认为那个例子还是挺生动的。&#xff08;狗头&#xff09; 不过今天我们要学习的是&#xff0c;发布订阅…

小侃设计模式(十八)-发布订阅模式

1.概述 发布订阅模式又叫观察者模式&#xff08;Observer Pattern&#xff09;&#xff0c;它是指对象之间一对多的依赖关系&#xff0c;每当那个特定对象改变状态时&#xff0c;所有依赖于它的对象都会得到通知并被自动更新&#xff0c;它是行为型模式的一种。观察者模式内部…

发布-订阅模式

发布-订阅模式 学习知识要善于思考&#xff0c;思考&#xff0c;再思考。 —— 爱因斯在众多设计模式中&#xff0c;可能最常见、最有名的就是发布 - 订阅模式了&#xff0c;本篇我们一起来学习这个模式。 发布 - 订阅模式 &#xff08;Publish-Subscribe Pattern, pub-sub&a…

什么是发布订阅模式?

发布-订阅模式&#xff08;Publish-Subscribe pattern&#xff09;是一种软件架构模式&#xff0c;用于实现组件之间的解耦和消息传递。在这种模式中&#xff0c;组件&#xff08;发布者&#xff09;将消息发送到一个中心&#xff08;消息代理或主题&#xff09;&#xff0c;然…

发布订阅模式

零、目录 应用场景实现原理代码实现全局模式下的订阅发布模式&#xff08;泛化的订阅发布模式&#xff09;总结 一、应用场景 ​ 发布订阅模式&#xff0c;广泛的存在于在我们的生活之中。 ​ 举个一个简单的例子来说&#xff0c;当我们在浏览视频或者博客论坛之类的网…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:VLCPlayer

本文简述如何在Smobiler中使用VLCPlayer插件&#xff0c;该插件支持播放rtsp流。 Step 1. 新建一个SmobilerForm窗体&#xff0c;再拖入VLCPlay&#xff0c;布局如下 在设计器中给VLCPlayer.Url赋值或者在窗体的Load事件中赋值 演示使用的rtsp流地址 rtsp://wowzaec2demo.strea…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:TTS

本文简述如何在Smobiler中使用TTS文字转语音。 Step 1. 新建一个SmobilerForm窗体&#xff0c;并在窗体中加入TTS和Button&#xff0c;布局如下 Button的点击事件代码&#xff1a; private void button1_Press(object sender, EventArgs e){ //第一个参数为文本&#xff1b;第…

Smobiler 仿得到APP个人主页

原型如下&#xff1a; 完整代码参考 https://github.com/comsmobiler/BlogsCode/blob/master/Source/BlogsCode_SmobilerForm/MyForm/dedao.cs 思路 可以将原型按照上图分成2个部分&#xff0c;部分A可以使用label、image、button、imagebutton、fontIcon控件来实现&#xff…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:PDFView

本文简述如何在Smobiler中使用PDFView。 Step 1. 新建一个SmobilerForm窗体&#xff0c;再拖入PDfView&#xff0c;布局如下 PDFView.ResourcrPath默认Document&#xff0c;指项目下\Resources\Document&#xff0c;若是pdf文件放在该文件夹下&#xff0c;则在设计器中直接赋值…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:OCR组件

本文简述如何在Smobiler中使用OCR组件进行文字识别。 Step 1. 新建一个SmobilerForm窗体&#xff0c;并在窗体中加入OCR和Button&#xff0c;布局如下 Button的点击事件代码&#xff1a; private void button1_Press(object sender, EventArgs e){ocr1.Recognize((obj,args)>…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:ArcFace人脸识别

本文简述如何在Smobiler中使用ArcFace&#xff08;虹软人脸识别&#xff09;。 Step 1. 新建一个SmobilerForm窗体&#xff0c;再拖入Button,Label,TextBox和AcrFace,布局如下 在设计器中给MediaView.Url赋值或者在窗体的Load事件中赋值 Button的事件代码如下 string message …

移动OA办公——Smobiler第一个开源应用解决方案,快来get吧

产品简介 SmoONE是一款移动OA类的开源解决方案&#xff0c;通过Smobiler平台开发&#xff0c;包含了注册、登陆、用户信息等基本功能。集成了OA中使用场景较多的报销、请假、部门管理、成本中心等核心功能。 免费获取方案 开源代码&#xff1a;https://github.com/comsmobile…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:UsbSerial串口通讯组件

本文简述如何在Smobiler中使用UsbSerial。 Step 1. 新建一个SmobilerForm窗体&#xff0c;再拖入UsbSerial和Button&#xff0c;布局如下 按钮事件代码&#xff1a; //连接private void button1_Press_2(object sender, EventArgs e){usbSerial1.Connect(Smobiler.Plugins.USBS…

Smobiler实现手机弹窗

前言 在实际项目中有很多场景需要用到弹窗&#xff0c;如图1 那么这些弹窗在Smobiler中如何实现呢&#xff1f; 正文 Smobiler实现弹窗有两种方式&#xff1a;1.MessageBox.Show 2.ShowDialog和ShowContextDialog。前者适合简易弹窗&#xff0c;后者适合自定义弹窗。 Messa…

Smobiler实现美观登录界面——C# 或.NET Smobiler实例开发手机app(二)

目录 一、 本文目标 二、 准备工作 1、 数据库 2、 材料 三、 界面布局 1、设置控件的属性值 &#xff08;1&#xff09; 输入框 &#xff08;2&#xff09; 图片属性 &#xff08;3&#xff09; HandElectricity的标题的label属性 &#xff08;4&#xff09;登录按钮…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:MediaView

本文简述如何在Smobiler中使用MediaView。 Step 1. 新建一个SmobilerForm窗体&#xff0c;再拖入MediaView&#xff0c;MediaView.Size设置&#xff08;300,225&#xff09;,布局如下 在设计器中给MediaView.Url赋值或者在窗体的Load事件中赋值 播放本地视频可以通过GetResourc…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:AliPay组件

本文简述如何在Smobiler中调用支付宝支付。 Step 1. 界面 新建一个窗体&#xff0c;并在窗体中拖入Button&#xff0c;Label&#xff0c;AliPay等控件&#xff0c;布局如下&#xff1a; Step 2. 代码 在窗体中声明变量 //订单编号private string tradeNo;//支付宝应用编号&am…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:BarcodeReader组件

本文简述如何在Smobiler中使用BarcodeReader组件进行条码识别。Barcodereader通过机器学习能识别不规则条码&#xff0c;效率更好。 Step 1. 新建一个SmobilerForm窗体&#xff0c;并在窗体中加入Barcodereader和Button&#xff0c;布局如下 Button的点击事件代码&#xff1a; …