🌈 个人主页:danci_
🔥 系列专栏:《设计模式》
💪🏻 制定明确可量化的目标,并坚持默默的做事。
探索发布-订阅模式的深度奥秘:实现高效、解耦的系统通信
文章目录
- 一、案例场景🔍
- 1.1 经典的运用场景
- 1.2 一坨坨代码实现😻
- 1.3 痛点
- 二、解决方案🚀
- 2.1 定义
- 2.2 案例分析🧐
- 2.3 模式结构图及说明
- 2.4 使用模式重构示例
- 2.5 重构后解决的问题👍
- 三、模式讲解🎭
- 3.1 认识发布订阅模式
- 3.2 实现步骤
3.3 思考中介者模式
- 四、总结🌟
- 4.1 优点💖
- 4.2 缺点
4.3 挑战和局限
一、案例场景🔍
1.1 经典的运用场景
发布-订阅模式在软件开发中拥有广泛的应用,它适用于多种场景,帮助开发者构建灵活、可扩展和松耦合的系统。以下是一些经典的应用场景及其在实际项目中的应用价值:👇
- 实时消息系统:
在聊天应用、社交媒体平台或实时数据流处理中,发布-订阅模式允许用户发送消息到一个中央通道,而其他用户可以根据兴趣订阅这些通道以接收实时更新。这降低了发送者和接收者之间的直接依赖,支持高并发和实时通信。 - 事件驱动架构:
在微服务或分布式系统中,服务之间通过事件进行通信。一个服务发布事件(如订单创建、用户注册等),而其他服务订阅这些事件并作出响应。这种异步通信方式提高了系统的响应性和可扩展性。 - 状态更新通知:
在用户界面或后台管理中,当某个状态发生变化时(如数据库记录更新),发布-订阅模式可以确保相关组件或用户得到通知。这有助于实现实时反馈和动态更新。 - 日志和监控:
系统日志和监控工具经常采用发布-订阅模式来收集、聚合和分发日志事件。发布者将日志事件发送到中央收集点,而订阅者可以是各种分析工具或警报系统。 - 物联网(IoT):
在物联网应用中,设备产生的大量数据需要被实时处理和分发。发布-订阅模式允许设备将数据发布到消息代理,而各种服务和应用程序可以订阅这些数据流以进行实时分析或响应。 - 股票交易和金融市场:
在金融应用中,实时数据流(如股票价格变动)对交易者至关重要。发布-订阅模式确保交易者能够订阅他们感兴趣的金融工具,并在价格变动时立即收到通知。
下面我们来实现实时消息系统场景 📄✏️。
1.2 一坨坨代码实现😻
要使用Java实现第一个场景(实时消息系统)而不直接使用发布-订阅设计模式,我们可以使用基础的Java并发和集合类来模拟这个系统。下面是一个简单的示例,展示了如何实现一个简易的实时聊天系统。
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
public class SimpleChatSystem {
// 存储所有用户的聊天室
private static final Map<String, List<ChatListener>> chatRooms = new HashMap<>();
// 用于在控制台模拟用户发送消息
public static void main(String[] args) {
// 创建两个用户
ChatListener alice = new ChatListener("Alice");
ChatListener bob = new ChatListener("Bob");
// 将用户添加到同一个聊天室
String chatRoomName = "General";
addToChatRoom(chatRoomName, alice);
addToChatRoom(chatRoomName, bob);
// 模拟Alice发送消息
sendMessage(chatRoomName, "Alice: Hello, Bob!");
// 模拟Bob发送消息
sendMessage(chatRoomName, "Bob: Hi, Alice! How are you?");
}
// 将用户添加到聊天室
public static void addToChatRoom(String chatRoomName, ChatListener listener) {
chatRooms.computeIfAbsent(chatRoomName, k -> new CopyOnWriteArrayList<>()).add(listener);
}
// 从聊天室移除用户
public static void removeFromChatRoom(String chatRoomName, ChatListener listener) {
chatRooms.getOrDefault(chatRoomName, new ArrayList<>()).remove(listener);
}
// 发送消息到聊天室
public static void sendMessage(String chatRoomName, String message) {
List<ChatListener> listeners = chatRooms.get(chatRoomName);
if (listeners != null) {
for (ChatListener listener : listeners) {
listener.receiveMessage(message);
}
}
}
// 简单的聊天监听器,用于接收消息并打印到控制台
public static class ChatListener implements Runnable {
private final String name;
public ChatListener(String name) {
this.name = name;
}
public void receiveMessage(String message) {
System.out.println(name + " received: " + message);
}
@Override
public void run() {
// 这个run方法没有实际用途,只是为了让ChatListener实现Runnable接口
// 在实际应用中,ChatListener可能会是一个复杂的类,包含网络连接、消息处理等逻辑
}
}
}
在这个示例中,我们没有使用任何设计模式,而是直接使用了Java的基础类。我们创建了一个SimpleChatSystem
类,它包含了一个静态的chatRooms
映射,用于存储聊天室和其中的用户(ChatListener
实例)。ChatListener
是一个简单的接口,它有一个receiveMessage
方法,用于接收并处理消息。
在main方法中,我们创建了两个用户(Alice和Bob),并将它们添加到同一个聊天室。然后,我们模拟Alice和Bob发送消息到聊天室,所有在聊天室中的用户都会接收到这些消息,并将它们打印到控制台。
注:上述实现只是个示例。在实际应用中,你可能需要考虑更多的因素,如网络编程、并发控制、错误处理、用户身份验证等。此外,如果你打算构建一个大规模的实时消息系统,使用专业的消息队列中间件(如RabbitMQ
、Kafka
等)可能是一个更好的选择。
问题描述:
用java实现第一个场景,要求不使用设计模式、编码逻辑思路清晰
问题描述:
上述实现是否有优点?
上述实现确实有一些优点,但也存在一些局限性。以下是对其优点的分析:👇
- 简单性:
✨ 这个实现非常直观和简单,易于理解和实现。它使用基础的Java类和并发控制来模拟实时消息系统,不需要掌握复杂的设计模式或框架。 - 演示目的:
✨ 作为一个示例,它很好地演示了如何使用Java的集合和并发特性来构建一个简单的实时通信系统。这对于初学者理解基本概念很有帮助。 - 静态模拟:
✨ 虽然它只在控制台中模拟用户之间的通信,但它可以作为一个起点,用于构建更复杂的系统。例如,可以将ChatListener
类扩展为包含网络连接、GUI界面或与其他服务集成的功能。 - 无额外依赖:
✨ 该实现不依赖于任何外部库或框架,这意味着它可以在任何标准的Java环境中运行,无需担心依赖管理或版本兼容性问题。
这个简单实现也有一些局限性,比如缺乏真正的异步通信、可扩展性限制、没有错误处理等。在实际项目中,通常会使用更复杂、更健壮的解决方案来处理这些问题。
1.3 痛点
然而,没有复杂的设计下上述实现确实存在一些缺点,尤其是在面对更复杂、大规模或生产级别的实时消息系统需求时。以下是对其缺点的分析:👇
-
缺乏真正的异步通信:
✨ 虽然示例中的ChatListener
可以接收消息,但整个系统并不是真正异步的。在真实世界的实时消息系统中,消息通常是通过异步方式发送和接收的,以提高响应性和吞吐量。 -
可扩展性限制:
✨ 由于使用了静态的映射来存储聊天室和用户,这个实现在处理大量用户或聊天室时可能会遇到性能瓶颈。此外,它也没有提供动态添加或删除聊天室的功能。 -
没有错误处理:
✨ 示例代码中没有包含任何错误处理逻辑。在实际应用中,网络中断、用户断开连接、消息格式错误等情况都需要妥善处理。 -
用户界面和交互性有限:
✨ 该实现仅在控制台打印接收到的消息,缺乏一个真正的用户界面或与其他服务的集成。这限制了其在现实世界应用中的实用性。 -
线程安全性问题:
✨ 虽然使用了CopyOnWriteArrayList
来提供一定程度的线程安全,但在更复杂的多线程环境中,这可能不足以保证系统的稳定性和数据的一致性。例如,当同时添加和删除用户时,可能会出现竞态条件。 -
单一的消息传递方式:
✨ 示例中只有一种简单的消息传递方式,即广播到整个聊天室。在实际应用中,可能需要更复杂的消息路由和传递策略,如点对点消息、组播、主题订阅等。 -
缺乏持久化:
✨ 消息在发送后被立即消费,没有持久化机制来存储历史消息或支持离线消息传递。 -
缺乏认证和授权:
✨ 示例中没有实现用户认证和授权机制,这在任何需要安全性的应用中都是必需的。
违反的设计原则(问题)下面逐一分析:👇
-
单一职责原则(SRP):
✨SimpleChatSystem
类负责了多个职责,包括管理聊天室、添加/移除用户以及发送消息。这可能导致类变得难以维护和扩展。更好的做法是将这些职责拆分到不同的类中,例如,可以有一个专门的ChatRoomManager
d 类来管理聊天室和用户,以及一个MessageSender
类来负责发送消息。 -
开闭原则(OCP):
✨ 当前的实现不够灵活,无法在不修改现有代码的情况下添加新的功能或行为。例如,如果想要添加新的聊天室类型或消息传递方式,可能需要修改SimpleChatSystem
类的代码。这违背了开闭原则,即软件实体应该对扩展开放,对修改关闭。 -
里氏替换原则(LSP):
✨ 虽然在这个简单的示例中没有明显的违反里氏替换原则的情况,但如果ChatListener
接口被扩展或修改,可能会导致子类无法正确替换父类或接口的问题。这通常发生在当子类没有遵守父类的约定或行为时。 -
接口隔离原则(ISP):
✨ChatListener
接口可能违反了接口隔离原则,因为它可能包含了不需要的方法(在这个简单示例中只有一个方法,但在更复杂的情况下可能会有更多)。如果接口过于庞大或包含不相关的方法,那么实现这个接口的类可能会被迫实现它们不需要的方法。 -
依赖倒置原则(DIP):
✨ 当前的实现中,高层模块(如SimpleChatSystem
)直接依赖于低层模块(如ChatListener),这可能导致代码的耦合度过高。更好的做法是使用抽象(如接口或抽象类)来定义依赖关系,这样高层模块就可以依赖于抽象而不是具体的实现。 -
迪米特法则(LoD)或最少知识原则:
✨ 在这个实现中,SimpleChatSystem
类直接访问和操作了ChatListener 实例的内部状态(通过调用receiveMessage
方法)。这违反了迪米特法则,即一个对象应该对其他对象保持最少的了解。更好的做法是通过定义清晰的接口和委托关系来减少类之间的直接依赖。
注:设计原则是为了指导软件设计而提出的,旨在提高代码的可维护性、可扩展性和可重用性。在实际项目中,根据具体的需求和上下文,可能需要权衡这些原则的应用。不过,在构建更大规模或更复杂的系统时,遵循这些原则通常是有益的。
二、解决方案🚀
2.1 定义
发布订阅模式:一种消息传递模型,发送者不直接将消息发送给接收者,而是发送到中间层。 |
接收者可以订阅这些消息,并在消息发布时接收到通知。这种模式的核心目的是实现发布者与订阅者之间的解耦,允许它们独立地扩展和修改,同时提供一种灵活的消息通信机制。通过发布订阅模式,系统可以更加灵活、可扩展和可维护。
2.2 案例分析🧐
2.3 模式结构图及说明
- 发布者(Publisher):
它是消息或者说主题的生产者,负责产生并发布消息到发布订阅管理器。发布者不需要知道或关心消息会被哪些订阅者接收,它只需要关注消息的发布。 - 订阅者(Subscriber)
它是订阅消息或主题的消费者,负责从发布订阅管理器接收并处理感兴趣的消息。订阅者需要提前向发布订阅管理器订阅自己感兴趣的主题,当发布者发布相关主题的消息时,发布订阅管理器会将消息推送给所有订阅了该主题的订阅者。 - 发布订阅管理器:
它是整个发布订阅模式的核心,负责接收并存储发布者发布的消息,同时管理订阅者的订阅信息。当发布者发布消息时,发布订阅管理器会根据订阅信息将消息推送给相应的订阅者。发布订阅管理器实现了发布者与订阅者之间的解耦,使得它们可以独立地扩展和修改。
发布订阅模式的功能是实现消息的发布与订阅,提供了一种灵活的消息通信机制,使得消息的发送者和接收者可以解耦,提高了系统的灵活性和可扩展性。同时,发布订阅模式还支持一对多、多对多的消息通信,可以满足复杂场景下的消息通信需求。
2.4 使用模式重构示例
为了解决上述实现中的缺点,并采用发布订阅模式重构实时消息系统,我们需要对原有的代码结构进行调整。以下是一个简化的重构示例:👇
- 首先,定义消息发布的接口和消息订阅的接口:
// 定义消息类型
public class Message {
private String sender;
private String content;
// 构造方法、getters和setters略...
}
// 消息发布接口
public interface MessagePublisher {
void publish(Message message);
}
// 消息订阅接口
public interface MessageSubscriber {
void onMessage(Message message);
}
// 消息订阅管理器,管理订阅者和消息的分发
public class MessageSubscriptionManager implements MessagePublisher {
private List<MessageSubscriber> subscribers = new CopyOnWriteArrayList<>();
@Override
public void publish(Message message) {
for (MessageSubscriber subscriber : subscribers) {
subscriber.onMessage(message);
}
}
public void subscribe(MessageSubscriber subscriber) {
subscribers.add(subscriber);
}
public void unsubscribe(MessageSubscriber subscriber) {
subscribers.remove(subscriber);
}
}
- 接下来,我们定义ChatRoom类,它将包含
MessageSubscriptionManager
实例来管理订阅和消息发布:
public class ChatRoom {
private String name;
private MessageSubscriptionManager subscriptionManager = new MessageSubscriptionManager();
public ChatRoom(String name) {
this.name = name;
}
public void sendMessage(String sender, String content) {
Message message = new Message(sender, content);
subscriptionManager.publish(message);
}
public void subscribe(MessageSubscriber subscriber) {
subscriptionManager.subscribe(subscriber);
}
public void unsubscribe(MessageSubscriber subscriber) {
subscriptionManager.unsubscribe(subscriber);
}
}
- 最后,实现一个简单的
MessageSubscriber
示例,它可以接收并处理来自ChatRoom
的消息:
public class ChatUser implements MessageSubscriber {
private String username;
public ChatUser(String username) {
this.username = username;
}
@Override
public void onMessage(Message message) {
System.out.println(username + " received message from " + message.getSender() + ": " + message.getContent());
}
// getters和setters略...
}
- 现在,创建
ChatRoom
和ChatUser
的实例,并将用户订阅到聊天室中,以接收实时消息:
public class RealTimeMessagingSystem {
public static void main(String[] args) {
ChatRoom chatRoom = new ChatRoom("General");
ChatUser user1 = new ChatUser("Alice");
ChatUser user2 = new ChatUser("Bob");
chatRoom.subscribe(user1);
chatRoom.subscribe(user2);
// 发送消息到聊天室
chatRoom.sendMessage("Server", "Hello, everyone!");
// ... 可以添加更多逻辑,比如用户之间的交互,或用户发送消息等。
// 当某个用户需要离开时,取消订阅
chatRoom.unsubscribe(user1);
}
}
<这个重构示例遵循了发布订阅模式,允许用户订阅聊天室的消息,并通过聊天室发送消息给所有订阅者。此外,通过 MessageSubscriptionManager
来管理订阅者和消息的分发,使得系统更加解耦、灵活,并支持多个用户之间的实时交互。这种模式使得系统易于扩展,能够适应不同数量和类型的订阅者和消息类型。
2.5 重构后解决的问题👍
优点
上述使用发布订阅模式的实现解决了以下已知缺点:👇
-
解耦:
✨ 在原始的实现中,发送者和接收者之间可能存在直接的依赖关系,这限制了系统的灵活性和可维护性。通过使用发布订阅模式,我们引入了中间层(MessageSubscriptionManager
),使得发送者(发布者)和接收者(订阅者)之间解耦。这意味着发送者不需要知道或关心消息被哪些接收者处理,同样,接收者也不需要知道消息来自哪个发送者。 -
可扩展性:
✨ 发布订阅模式允许系统更容易地扩展。无论是增加新的订阅者还是处理更多的消息类型,都不需要对现有代码进行大量修改。在重构后的代码中,添加新的订阅者只需要调用subscribe
方法,而移除订阅者则调用unsubscribe
方法。 -
实时性与异步处理:
✨ 发布订阅模式天然支持异步消息传递。在重构后的系统中,消息的发送是异步的,发送者不需要等待接收者的响应。这提高了系统的响应性和吞吐量,使得它能够更好地处理实时消息。 -
容错性与可靠性:
✨ 虽然上述示例代码没有直接展示容错性机制,但发布订阅模式为实现容错性和可靠性提供了基础。例如,中间层可以设计为支持消息的持久化存储,以便在订阅者暂时不可用时保留消息。此外,通过监控和重试机制,可以确保消息最终被传递给订阅者。 -
灵活性:
✨ 发布订阅模式使得系统更加灵活,能够适应不同的业务需求。例如,订阅者可以选择性地订阅感兴趣的消息类型,或者根据消息的内容执行不同的操作。这种灵活性使得系统更容易适应变化,降低了维护成本。
遵循的设计原则
上述使用发布-订阅模式重构示例后的代码遵循了以下设计原则:👇
-
单一职责原则(Single Responsibility Principle, SRP):
✈️ 每个类(如Message
,MessagePublisher
,MessageSubscriber
,MessageSubscriptionManager
,ChatRoom
,ChatUser
)都只负责一项功能或职责。例如,Message
类只负责封装消息内容,而MessageSubscriptionManager
只负责管理订阅和消息的分发。 -
开闭原则(Open-Closed Principle, OCP):
✈️ 系统应该对扩展开放,对修改关闭。在上述实现中,如果需要添加新的消息类型或订阅者,不需要修改现有的代码,只需扩展相应的接口或类即可。例如,可以通过实现MessageSubscriber接口来创建新的订阅者。 -
里氏替换原则(Liskov Substitution Principle, LSP):
✈️ 子类必须能够替换其父类出现在任何地方,并且不会引入任何错误或异常。虽然上述示例中没有直接展示继承关系,但如果有子类继承自MessageSubscriber
,那么它们应该能够无缝地替换父类实例,而不需要修改MessageSubscriptionManager
或ChatRoom
的代码。 -
接口隔离原则(Interface Segregation Principle, ISP):
✈️ 客户端不应该依赖于它不使用的接口。在上述实现中,MessagePublisher
和MessageSubscriber
接口都是小而专注的,只定义了与发布和订阅消息相关的操作,没有包含任何不相关的方法。 -
依赖倒置原则(Dependency Inversion Principle, DIP):
✈️ 高层模块不应该依赖于低层模块,它们都应该依赖于抽象。在上述实现中,ChatRoom
类依赖于抽象的MessagePublisher
接口(通过MessageSubscriptionManager
实现),而不是具体的实现类。这意味着如果需要更换消息发布的管理方式,只需要提供一个新的MessagePublisher
实现,而不需要修改ChatRoom的代码。 -
迪米特法则(Law of Demeter, LoD)或最少知识原则(Least Knowledge Principle, LKP)::
✈️ 一个对象应该对其他对象保持最少的了解。在上述实现中,ChatRoom
只与MessageSubscriptionManager
交互,而不直接与订阅者交互。同样,订阅者(如ChatUser
)也只与接收到的消息交互,而不需要知道消息是如何发布或管理的。
缺点
上述实现(假设是指使用发布订阅模式的实现)虽然解决了许多已知的问题,并遵循了良好的设计原则,但仍然可能存在一些潜在的缺点或考虑因素:👇
-
内存消耗:
💡 如果订阅者数量众多或者消息产生频率非常高,系统可能会面临内存压力。因为每个订阅者都可能保留一份消息副本,或者中间层(如MessageSubscriptionManager
)需要维护大量的订阅关系和待处理消息。 -
消息处理顺序:
💡 发布订阅模式通常不保证消息的处理顺序。如果有多个订阅者同时处理同一条消息,或者订阅者在不同线程/进程中运行,那么消息的处理顺序可能会变得不可预测。 -
消息丢失或重复:
💡 在分布式系统或存在网络延迟/故障的环境中,发布订阅模式可能会面临消息丢失或重复的问题。需要额外的机制来确保消息的可靠传输和幂等性处理。 -
复杂性增加:
💡 引入发布订阅模式可能会增加系统的复杂性。需要管理额外的组件(如消息代理、订阅管理器等),并处理它们之间的交互和依赖关系。此外,调试和维护一个复杂的消息传递系统也可能更加困难。 -
性能瓶颈:
💡 如果所有消息都通过一个集中的MessageSubscriptionManager
进行处理和分发,那么它可能会成为系统的性能瓶颈。特别是在高并发或大数据量的场景下,单点压力可能会很大。 -
安全性考虑:
💡 发布订阅模式可能暴露更多的攻击面。例如,未经授权的订阅者可能会接收到敏感信息,或者恶意订阅者可能会发送伪造的消息来干扰系统的正常运行。
三、模式讲解🎭
核心思想
通过引入一个中间层来实现发布者和订阅者之间的解耦通信,支持事件驱动、基于兴趣的注册、异步通信以及高度的可扩展性和灵活性。 |
3.1 认识发布订阅模式
发布订阅模式是一种消息传递模型,它定义了如何在发布者和订阅者之间传递消息。在这种模式中,发布者(也称为发送者或生产者)生成并发送消息到一个中间层,而不需要知道或关心哪些订阅者(也称为接收者或消费者)会接收这些消息。订阅者则向中间层注册自己的兴趣,以便在相关消息发布时接收它们。
发布订阅模式具有以下功能:👇
-
消息发布:
🌈 发布者能够生成消息并将其发送到中间层。这些消息可以是事件通知、状态更新、数据变更等。发布者不需要知道订阅者的具体信息,只需将消息发送到中间层即可。 -
消息订阅:
🌈 订阅者能够向中间层注册自己的兴趣,表示它们希望接收特定类型的消息。订阅者可以指定自己感兴趣的消息类型、主题或标签,以便在相关消息发布时得到通知。 -
消息分发:
🌈 中间层负责接收发布者发送的消息,并根据订阅者的兴趣将消息分发给相应的订阅者。这个过程是自动的,订阅者不需要主动轮询或请求消息,而是被动地接收中间层推送的消息。 -
解耦:
🌈 发布订阅模式实现了发布者和订阅者之间的解耦。它们不需要直接相互依赖或了解对方的存在。这种解耦使得系统更加灵活,易于扩展和维护。 -
异步通信:
🌈 发布订阅模式支持异步通信。消息的发送和接收不需要同步进行,发布者可以在任何时间发送消息,而订阅者则可以在稍后的时间异步地接收和处理这些消息。这种异步通信机制有助于提高系统的响应性和吞吐量。 -
可扩展性:
🌈 由于发布者和订阅者之间的解耦以及中间层的存在,发布订阅模式提供了很高的可扩展性。新的发布者或订阅者可以很容易地添加到系统中,而不需要修改现有的代码或配置。此外,系统可以支持多种不同类型的事件和消息,以满足不同的业务需求。 -
容错性和可靠性:
🌈 发布订阅模式可以支持消息的持久化存储和重试机制,以确保消息在传输过程中的可靠性和容错性。即使订阅者暂时不可用或网络出现故障,消息也不会丢失,而是会在适当的时候重新发送给订阅者。
3.2 实现步骤
发布订阅模式是一种在软件工程中广泛使用的设计模式,它定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题(或事件)。当该主题(或事件)在发布者对象上触发时,所有订阅了该主题的订阅者对象都会自动接收到通知。
以下是实现发布订阅模式的基本步骤:👇
-
确定发布者和订阅者:
⌛ 首先,需要明确系统中的哪些对象将充当发布者,哪些对象将充当订阅者。发布者负责产生并发送事件或消息,而订阅者则负责接收并处理这些事件或消息。 -
创建事件通道或消息代理:
⌛ 这是发布者和订阅者之间的中间层,用于管理事件或消息的传递。发布者将事件发送到这个中间层,而订阅者则从这个中间层接收事件。这个中间层可以是一个事件总线、消息队列、或者任何能够实现发布者和订阅者之间解耦的机制。 -
注册订阅者:
⌛ 订阅者需要向事件通道或消息代理注册自己的兴趣,即它们希望接收哪些类型的事件或消息。这通常通过调用事件通道或消息代理的注册方法来实现,注册时需要提供订阅者的标识符和它们感兴趣的事件类型。 -
发布事件:
⌛ 当发布者产生一个事件时,它会将该事件发送到事件通道或消息代理。发布者不需要知道有哪些订阅者正在监听这个事件,也不需要直接与订阅者进行通信。事件的发布通常通过调用事件通道或消息代理的发布方法来实现,发布时需要提供事件的类型和相关的数据。 -
分发事件:
⌛ 事件通道或消息代理接收到事件后,会根据注册信息将事件分发给所有对该类型事件感兴趣的订阅者。这个过程是自动的,订阅者不需要主动请求或轮询事件。事件通道或消息代理负责确保事件能够正确地分发给所有相关的订阅者。 -
处理事件:
⌛ 订阅者接收到事件后,会根据自己的业务逻辑对事件进行处理。处理的方式取决于具体的业务需求和订阅者的实现。处理完事件后,订阅者可以选择是否继续监听该类型的事件,或者注销自己的订阅。
发布订阅模式允许发布者和订阅者之间实现解耦的通信,提高了系统的灵活性和可扩展性。同时,由于事件的分发是自动的,因此也提高了系统的响应速度和吞吐量。
3.3 思考中介者模式
相似的设计模式
1. 观察者模式(Observer Pattern)
相似点
一对多依赖:两者都处理一对多的依赖关系,即一个对象(发布者/主题)改变其状态时,多个对象(订阅者/观察者)会收到通知。
解耦:两者都实现了对象之间的解耦,允许它们独立地改变和演化。
易混淆点
通知机制:发布订阅模式通常依赖于一个中间层(如事件总线)来传递消息,而观察者模式则直接由主题对象通知其观察者。
异步性:发布订阅模式通常支持异步通信,而观察者模式通常是同步的。
2. 中介者模式(Mediator Pattern)
相似点
集中控制:两者都通过一个中间层(发布订阅模式中的事件通道或消息代理,中介者模式中的中介者对象)来集中控制对象间的交互。
减少依赖:两者都减少了对象之间的直接依赖,使得系统更加灵活和可维护。
易混淆点
职责:中介者模式通常负责协调多个对象之间的复杂交互,而发布订阅模式更专注于事件的发布和订阅。
通信类型:中介者模式通常处理请求/响应类型的通信,而发布订阅模式则更侧重于事件的通知和分发。
容易混淆的设计模式
1. 消息队列模式(Message Queue Pattern)
易混淆点
异步通信:发布订阅模式和消息队列模式都支持异步通信,使得发送者和接收者可以独立地工作。
中间件:两者都依赖于一个中间件(发布订阅模式中的事件通道或消息代理,消息队列模式中的消息队列)来进行消息的传递。
区分点
持久性:消息队列通常支持消息的持久化存储,即使在系统崩溃或重启后也能保证消息的可靠传递。而发布订阅模式则可能只是临时传递消息。
用途:消息队列通常用于构建分布式系统或微服务架构中的通信机制,而发布订阅模式则更广泛地应用于各种需要解耦通信的场景。
2. 责任链模式(Chain of Responsibility Pattern)
易混淆点
多个处理者:在责任链模式和发布订阅模式中,都可能涉及多个对象或组件来处理请求或事件。
区分点
处理顺序:责任链模式按照预设的顺序依次将请求传递给链中的对象,直到某个对象处理它或链结束。而发布订阅模式则是同时通知所有订阅者。
处理结果:在责任链模式中,每个对象都可以决定是否继续传递请求,而在发布订阅模式中,发布者不关心订阅者如何处理事件。
这些设计模式在某些方面有相似之处,但也存在明显的差异。理解这些相似点和易混淆点有助于更准确地选择和应用设计模式,以满足特定的软件设计需求。
四、总结🌟
4.1 优点💖
发布订阅模式在松耦合、关注点分离、高伸缩性、高可靠性和异步通信等方面具有显著优点。这使得它在许多复杂和分布式系统中得到广泛应用,成为实现高效、可靠和可扩展通信的重要手段之一。然而,也需要注意到该模式可能引入一些额外的复杂性,如中间件的管理和维护等,因此在应用时需要权衡其优缺点并根据具体场景进行选择。以下是对发布订阅模式优点的详细描述:👇
-
松耦合与独立性:
❤️ 发布订阅模式的核心优点之一在于它能够将众多需要通信的子系统或组件解耦。这意味着发送者和接收者之间不需要直接了解彼此的存在或实现细节。每个子系统或组件都可以独立地管理自己的逻辑和状态,而不需要关心其他部分的变化。这种松耦合的特性使得系统更加灵活,可以更容易地添加、删除或修改子系统,而不会对整个系统的结构造成大的影响。 -
关注点分离:
❤️ 在发布订阅模式中,发送者(发布者)的主要职责是发布消息或事件,而接收者(订阅者)则关注于处理这些消息或事件。这样,每个组件都可以专注于其核心功能,而不必担心与其他组件的交互细节。这种关注点分离使得代码更加清晰、易于理解和维护。 -
高伸缩性:
❤️ 发布订阅模式具有出色的伸缩性。由于发送者和接收者之间是解耦的,因此系统可以轻松地处理大量的并发请求或事件。此外,通过增加更多的订阅者或优化消息传递机制,系统可以进一步扩展其处理能力。这种伸缩性使得发布订阅模式非常适合处理大规模、高并发的应用场景。 -
高可靠性:
❤️ 发布订阅模式通过异步通信和消息队列等方式,提高了系统的可靠性。即使部分子系统或组件出现故障或下线,也不会影响整个系统的消息传递和事件处理。此外,通过消息持久化、重试机制等技术手段,可以确保消息在传输过程中的可靠性和完整性。这种高可靠性使得系统更加稳定、可依赖。 -
异步通信:
❤️ 发布订阅模式支持异步通信,这意味着发送者不需要等待接收者处理完消息后再继续执行其他任务。这种异步性使得系统可以更加高效地利用资源,提高响应速度和处理能力。同时,异步通信也减少了系统之间的耦合度,使得系统更加灵活和可扩展。
4.2 缺点
发布订阅模式虽然具有许多优点,但也存在一些缺点和挑战。在应用该模式时,需要权衡其优缺点,并根据具体场景和需求进行选择。同时,需要采取适当的设计和实现策略来缓解这些缺点带来的问题。例如,可以通过引入消息顺序保证机制、优化消息代理的性能、加强系统的安全性和可靠性等措施来改进发布订阅模式的实现。以下是对发布订阅模式缺点的详细描述:👇
-
消息传递的顺序问题:
💔 在发布订阅模式中,消息的顺序不能得到保证。如果订阅者之间有依赖关系,或者某些操作需要按照特定顺序执行,那么这可能会成为一个问题。因为发布者只是简单地将消息发送给所有订阅者,而不关心它们如何处理或消费这些消息。 -
消息代理的性能开销:
💔 发布订阅模式中通常需要引入一个消息代理或中间件来管理消息的发布和订阅。这可能会带来额外的性能开销,包括内存占用、处理延迟等。特别是在处理大量消息或高并发场景时,这种性能开销可能会更加明显。 -
复杂性增加:
💔 发布订阅模式可能增加系统的复杂性。由于引入了额外的组件和通信机制,系统的架构和设计可能变得更加复杂。此外,多个发布者和订阅者之间的交互也可能导致难以追踪和调试的问题。 -
可能的内存泄漏:
💔 如果订阅者忘记取消订阅,或者由于某些原因无法处理接收到的消息,那么这可能会导致内存泄漏。因为消息代理可能会持续地向这些订阅者发送消息,占用越来越多的内存资源。 -
安全性问题:
💔 发布订阅模式可能面临安全性挑战。由于消息是在发布者和订阅者之间传递的,因此可能存在未经授权的访问、数据泄露或篡改等风险。需要采取适当的安全措施来保护消息的安全性和完整性。
4.3 挑战和局限
发布订阅模式作为一种流行的软件设计模式,确实为构建松耦合、可扩展和异步通信的系统提供了强大的支持。然而,它并非没有局限和挑战。以下是对发布订阅模式面临的主要挑战和局限的详细描述:👇
挑战
-
消息顺序保证:
💖 在并发环境中,确保消息按照预期的顺序被处理是一个挑战。发布订阅模式本身不提供消息排序的机制,这可能导致订阅者接收到乱序的消息。 -
消息过滤和路由:
🤩 当系统中有大量的发布者和订阅者时,有效地过滤和路由消息变得复杂。需要设计高效的策略来确保消息能够准确地传递给感兴趣的订阅者,同时避免不必要的消息传递。 -
系统监控和调试:
🌈 由于发布订阅模式的异步和分布式特性,监控和调试变得困难。跟踪消息的流动、识别和处理故障点需要额外的工具和日志记录策略。 -
性能优化:
🚀 在高负载下,消息代理或事件总线可能成为性能瓶颈。需要优化这些组件以处理大量的消息流,并确保系统的响应时间和吞吐量满足需求。 -
安全性保障:
🎭 确保消息在传输和存储过程中的安全性是一个重要挑战。需要实施加密、身份验证和访问控制等安全措施来保护敏感数据免受未经授权的访问和篡改。
局限
-
不适合实时性要求极高的场景:
😂 由于消息传递的异步性和可能的延迟,发布订阅模式可能不适合那些对实时性要求极高的应用,如金融交易系统或实时控制系统。 -
额外的管理和维护开销:
😢 引入消息代理或事件总线增加了系统的复杂性,需要额外的管理和维护工作。这包括配置、部署、监控和升级中间件等任务。 -
可能的消息丢失:
🧐 在分布式环境中,网络故障、进程崩溃或存储故障可能导致消息丢失。虽然可以通过持久化、重试和确认机制来减少这种风险,但完全避免消息丢失是困难的。 -
数据一致性挑战:
🤔 在分布式系统中使用发布订阅模式时,确保数据的一致性和完整性是一个挑战。特别是在处理跨多个订阅者的状态时,需要仔细设计更新策略和同步机制。 -
订阅者依赖管理:
😅 当订阅者之间存在依赖关系时,管理这些依赖关系可能变得复杂。需要确保订阅者以正确的顺序接收到消息,并处理可能的依赖冲突。