探索设计模式的魅力:探索发布-订阅模式的深度奥秘-实现高效、解耦的系统通信

news2024/11/25 7:10:48

在这里插入图片描述
​🌈 个人主页: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 经典的运用场景

    发布-订阅模式在软件开发中拥有广泛的应用,它适用于多种场景,帮助开发者构建灵活、可扩展和松耦合的系统。以下是一些经典的应用场景及其在实际项目中的应用价值:👇

  1. 实时消息系统:
    在聊天应用、社交媒体平台或实时数据流处理中,发布-订阅模式允许用户发送消息到一个中央通道,而其他用户可以根据兴趣订阅这些通道以接收实时更新。这降低了发送者和接收者之间的直接依赖,支持高并发和实时通信。
  2. 事件驱动架构:
    在微服务或分布式系统中,服务之间通过事件进行通信。一个服务发布事件(如订单创建、用户注册等),而其他服务订阅这些事件并作出响应。这种异步通信方式提高了系统的响应性和可扩展性。
  3. 状态更新通知:
    在用户界面或后台管理中,当某个状态发生变化时(如数据库记录更新),发布-订阅模式可以确保相关组件或用户得到通知。这有助于实现实时反馈和动态更新。
  4. 日志和监控:
    系统日志和监控工具经常采用发布-订阅模式来收集、聚合和分发日志事件。发布者将日志事件发送到中央收集点,而订阅者可以是各种分析工具或警报系统。
  5. 物联网(IoT):
    在物联网应用中,设备产生的大量数据需要被实时处理和分发。发布-订阅模式允许设备将数据发布到消息代理,而各种服务和应用程序可以订阅这些数据流以进行实时分析或响应。
  6. 股票交易和金融市场:
    在金融应用中,实时数据流(如股票价格变动)对交易者至关重要。发布-订阅模式确保交易者能够订阅他们感兴趣的金融工具,并在价格变动时立即收到通知。

    下面我们来实现实时消息系统场景 📄✏️。

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发送消息到聊天室,所有在聊天室中的用户都会接收到这些消息,并将它们打印到控制台。

    注:上述实现只是个示例。在实际应用中,你可能需要考虑更多的因素,如网络编程、并发控制、错误处理、用户身份验证等。此外,如果你打算构建一个大规模的实时消息系统,使用专业的消息队列中间件(如RabbitMQKafka等)可能是一个更好的选择。

问题描述:
用java实现第一个场景,要求不使用设计模式、编码逻辑思路清晰

问题描述:
上述实现是否有优点?

    上述实现确实有一些优点,但也存在一些局限性。以下是对其优点的分析:👇

  1. 简单性:
    ✨ 这个实现非常直观和简单,易于理解和实现。它使用基础的Java类和并发控制来模拟实时消息系统,不需要掌握复杂的设计模式或框架。
  2. 演示目的:
    ✨ 作为一个示例,它很好地演示了如何使用Java的集合和并发特性来构建一个简单的实时通信系统。这对于初学者理解基本概念很有帮助。
  3. 静态模拟:
    ✨ 虽然它只在控制台中模拟用户之间的通信,但它可以作为一个起点,用于构建更复杂的系统。例如,可以将 ChatListener 类扩展为包含网络连接、GUI界面或与其他服务集成的功能。
  4. 无额外依赖:
    ✨ 该实现不依赖于任何外部库或框架,这意味着它可以在任何标准的Java环境中运行,无需担心依赖管理或版本兼容性问题。

    这个简单实现也有一些局限性,比如缺乏真正的异步通信、可扩展性限制、没有错误处理等。在实际项目中,通常会使用更复杂、更健壮的解决方案来处理这些问题。

1.3 痛点

在这里插入图片描述

    然而,没有复杂的设计下上述实现确实存在一些缺点,尤其是在面对更复杂、大规模或生产级别的实时消息系统需求时。以下是对其缺点的分析:👇

  1. 缺乏真正的异步通信:
    ✨ 虽然示例中的 ChatListener 可以接收消息,但整个系统并不是真正异步的。在真实世界的实时消息系统中,消息通常是通过异步方式发送和接收的,以提高响应性和吞吐量。

  2. 可扩展性限制:
    ✨ 由于使用了静态的映射来存储聊天室和用户,这个实现在处理大量用户或聊天室时可能会遇到性能瓶颈。此外,它也没有提供动态添加或删除聊天室的功能。

  3. 没有错误处理:
    ✨ 示例代码中没有包含任何错误处理逻辑。在实际应用中,网络中断、用户断开连接、消息格式错误等情况都需要妥善处理。

  4. 用户界面和交互性有限:
    ✨ 该实现仅在控制台打印接收到的消息,缺乏一个真正的用户界面或与其他服务的集成。这限制了其在现实世界应用中的实用性。

  5. 线程安全性问题:
    ✨ 虽然使用了 CopyOnWriteArrayList 来提供一定程度的线程安全,但在更复杂的多线程环境中,这可能不足以保证系统的稳定性和数据的一致性。例如,当同时添加和删除用户时,可能会出现竞态条件。

  6. 单一的消息传递方式:
    ✨ 示例中只有一种简单的消息传递方式,即广播到整个聊天室。在实际应用中,可能需要更复杂的消息路由和传递策略,如点对点消息、组播、主题订阅等。

  7. 缺乏持久化:
    ✨ 消息在发送后被立即消费,没有持久化机制来存储历史消息或支持离线消息传递。

  8. 缺乏认证和授权:
    ✨ 示例中没有实现用户认证和授权机制,这在任何需要安全性的应用中都是必需的。

    违反的设计原则(问题)下面逐一分析:👇

  1. 单一职责原则(SRP):
    SimpleChatSystem 类负责了多个职责,包括管理聊天室、添加/移除用户以及发送消息。这可能导致类变得难以维护和扩展。更好的做法是将这些职责拆分到不同的类中,例如,可以有一个专门的 ChatRoomManagerd 类来管理聊天室和用户,以及一个 MessageSender 类来负责发送消息。

  2. 开闭原则(OCP):
    ✨ 当前的实现不够灵活,无法在不修改现有代码的情况下添加新的功能或行为。例如,如果想要添加新的聊天室类型或消息传递方式,可能需要修改 SimpleChatSystem 类的代码。这违背了开闭原则,即软件实体应该对扩展开放,对修改关闭。

  3. 里氏替换原则(LSP):
    ✨ 虽然在这个简单的示例中没有明显的违反里氏替换原则的情况,但如果 ChatListener 接口被扩展或修改,可能会导致子类无法正确替换父类或接口的问题。这通常发生在当子类没有遵守父类的约定或行为时。

  4. 接口隔离原则(ISP):
    ChatListener 接口可能违反了接口隔离原则,因为它可能包含了不需要的方法(在这个简单示例中只有一个方法,但在更复杂的情况下可能会有更多)。如果接口过于庞大或包含不相关的方法,那么实现这个接口的类可能会被迫实现它们不需要的方法。

  5. 依赖倒置原则(DIP):
    ✨ 当前的实现中,高层模块(如SimpleChatSystem)直接依赖于低层模块(如ChatListener),这可能导致代码的耦合度过高。更好的做法是使用抽象(如接口或抽象类)来定义依赖关系,这样高层模块就可以依赖于抽象而不是具体的实现。

  6. 迪米特法则(LoD)或最少知识原则:
    ✨ 在这个实现中,SimpleChatSystem 类直接访问和操作了ChatListener 实例的内部状态(通过调用 receiveMessage 方法)。这违反了迪米特法则,即一个对象应该对其他对象保持最少的了解。更好的做法是通过定义清晰的接口和委托关系来减少类之间的直接依赖。

    注:设计原则是为了指导软件设计而提出的,旨在提高代码的可维护性、可扩展性和可重用性。在实际项目中,根据具体的需求和上下文,可能需要权衡这些原则的应用。不过,在构建更大规模或更复杂的系统时,遵循这些原则通常是有益的。

二、解决方案🚀

2.1 定义

发布订阅模式:一种消息传递模型,发送者不直接将消息发送给接收者,而是发送到中间层。

    接收者可以订阅这些消息,并在消息发布时接收到通知。这种模式的核心目的是实现发布者与订阅者之间的解耦,允许它们独立地扩展和修改,同时提供一种灵活的消息通信机制。通过发布订阅模式,系统可以更加灵活、可扩展和可维护。

2.2 案例分析🧐

在这里插入图片描述

2.3 模式结构图及说明

在这里插入图片描述

  • 发布者(Publisher):
    它是消息或者说主题的生产者,负责产生并发布消息到发布订阅管理器。发布者不需要知道或关心消息会被哪些订阅者接收,它只需要关注消息的发布。
  • 订阅者(Subscriber)
    它是订阅消息或主题的消费者,负责从发布订阅管理器接收并处理感兴趣的消息。订阅者需要提前向发布订阅管理器订阅自己感兴趣的主题,当发布者发布相关主题的消息时,发布订阅管理器会将消息推送给所有订阅了该主题的订阅者。
  • 发布订阅管理器:
    它是整个发布订阅模式的核心,负责接收并存储发布者发布的消息,同时管理订阅者的订阅信息。当发布者发布消息时,发布订阅管理器会根据订阅信息将消息推送给相应的订阅者。发布订阅管理器实现了发布者与订阅者之间的解耦,使得它们可以独立地扩展和修改。

    发布订阅模式的功能是实现消息的发布与订阅,提供了一种灵活的消息通信机制,使得消息的发送者和接收者可以解耦,提高了系统的灵活性和可扩展性。同时,发布订阅模式还支持一对多、多对多的消息通信,可以满足复杂场景下的消息通信需求。

2.4 使用模式重构示例

    为了解决上述实现中的缺点,并采用发布订阅模式重构实时消息系统,我们需要对原有的代码结构进行调整。以下是一个简化的重构示例:👇

  1. 首先,定义消息发布的接口和消息订阅的接口:
// 定义消息类型  
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);  
    }  
}
  1. 接下来,我们定义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);  
    }  
}
  1. 最后,实现一个简单的 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略...  
}
  1. 现在,创建 ChatRoomChatUser 的实例,并将用户订阅到聊天室中,以接收实时消息:
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 重构后解决的问题👍

在这里插入图片描述

    优点
    上述使用发布订阅模式的实现解决了以下已知缺点:👇

  1. 解耦:
    ✨ 在原始的实现中,发送者和接收者之间可能存在直接的依赖关系,这限制了系统的灵活性和可维护性。通过使用发布订阅模式,我们引入了中间层(MessageSubscriptionManager),使得发送者(发布者)和接收者(订阅者)之间解耦。这意味着发送者不需要知道或关心消息被哪些接收者处理,同样,接收者也不需要知道消息来自哪个发送者。

  2. 可扩展性:
    ✨ 发布订阅模式允许系统更容易地扩展。无论是增加新的订阅者还是处理更多的消息类型,都不需要对现有代码进行大量修改。在重构后的代码中,添加新的订阅者只需要调用 subscribe 方法,而移除订阅者则调用 unsubscribe 方法。

  3. 实时性与异步处理:
    ✨ 发布订阅模式天然支持异步消息传递。在重构后的系统中,消息的发送是异步的,发送者不需要等待接收者的响应。这提高了系统的响应性和吞吐量,使得它能够更好地处理实时消息。

  4. 容错性与可靠性:
    ✨ 虽然上述示例代码没有直接展示容错性机制,但发布订阅模式为实现容错性和可靠性提供了基础。例如,中间层可以设计为支持消息的持久化存储,以便在订阅者暂时不可用时保留消息。此外,通过监控和重试机制,可以确保消息最终被传递给订阅者。

  5. 灵活性:
    ✨ 发布订阅模式使得系统更加灵活,能够适应不同的业务需求。例如,订阅者可以选择性地订阅感兴趣的消息类型,或者根据消息的内容执行不同的操作。这种灵活性使得系统更容易适应变化,降低了维护成本。

    遵循的设计原则
    上述使用发布-订阅模式重构示例后的代码遵循了以下设计原则:👇

  1. 单一职责原则(Single Responsibility Principle, SRP):
    ✈️ 每个类(如 Message , MessagePublisher , MessageSubscriber , MessageSubscriptionManager , ChatRoom , ChatUser )都只负责一项功能或职责。例如,Message 类只负责封装消息内容,而 MessageSubscriptionManager 只负责管理订阅和消息的分发。

  2. 开闭原则(Open-Closed Principle, OCP):
    ✈️ 系统应该对扩展开放,对修改关闭。在上述实现中,如果需要添加新的消息类型或订阅者,不需要修改现有的代码,只需扩展相应的接口或类即可。例如,可以通过实现MessageSubscriber接口来创建新的订阅者。

  3. 里氏替换原则(Liskov Substitution Principle, LSP):
    ✈️ 子类必须能够替换其父类出现在任何地方,并且不会引入任何错误或异常。虽然上述示例中没有直接展示继承关系,但如果有子类继承自 MessageSubscriber ,那么它们应该能够无缝地替换父类实例,而不需要修改 MessageSubscriptionManagerChatRoom 的代码。

  4. 接口隔离原则(Interface Segregation Principle, ISP):
    ✈️ 客户端不应该依赖于它不使用的接口。在上述实现中, MessagePublisherMessageSubscriber 接口都是小而专注的,只定义了与发布和订阅消息相关的操作,没有包含任何不相关的方法。

  5. 依赖倒置原则(Dependency Inversion Principle, DIP):
    ✈️ 高层模块不应该依赖于低层模块,它们都应该依赖于抽象。在上述实现中, ChatRoom 类依赖于抽象的 MessagePublisher 接口(通过 MessageSubscriptionManager 实现),而不是具体的实现类。这意味着如果需要更换消息发布的管理方式,只需要提供一个新的 MessagePublisher 实现,而不需要修改ChatRoom的代码。

  6. 迪米特法则(Law of Demeter, LoD)或最少知识原则(Least Knowledge Principle, LKP)::
    ✈️ 一个对象应该对其他对象保持最少的了解。在上述实现中, ChatRoom 只与 MessageSubscriptionManager 交互,而不直接与订阅者交互。同样,订阅者(如 ChatUser )也只与接收到的消息交互,而不需要知道消息是如何发布或管理的。

    缺点
    上述实现(假设是指使用发布订阅模式的实现)虽然解决了许多已知的问题,并遵循了良好的设计原则,但仍然可能存在一些潜在的缺点或考虑因素:👇

  1. 内存消耗:
    💡 如果订阅者数量众多或者消息产生频率非常高,系统可能会面临内存压力。因为每个订阅者都可能保留一份消息副本,或者中间层(如 MessageSubscriptionManager )需要维护大量的订阅关系和待处理消息。

  2. 消息处理顺序:
    💡 发布订阅模式通常不保证消息的处理顺序。如果有多个订阅者同时处理同一条消息,或者订阅者在不同线程/进程中运行,那么消息的处理顺序可能会变得不可预测。

  3. 消息丢失或重复:
    💡 在分布式系统或存在网络延迟/故障的环境中,发布订阅模式可能会面临消息丢失或重复的问题。需要额外的机制来确保消息的可靠传输和幂等性处理。

  4. 复杂性增加:
    💡 引入发布订阅模式可能会增加系统的复杂性。需要管理额外的组件(如消息代理、订阅管理器等),并处理它们之间的交互和依赖关系。此外,调试和维护一个复杂的消息传递系统也可能更加困难。

  5. 性能瓶颈:
    💡 如果所有消息都通过一个集中的 MessageSubscriptionManager 进行处理和分发,那么它可能会成为系统的性能瓶颈。特别是在高并发或大数据量的场景下,单点压力可能会很大。

  6. 安全性考虑:
    💡 发布订阅模式可能暴露更多的攻击面。例如,未经授权的订阅者可能会接收到敏感信息,或者恶意订阅者可能会发送伪造的消息来干扰系统的正常运行。

三、模式讲解🎭

在这里插入图片描述

  核心思想

通过引入一个中间层来实现发布者和订阅者之间的解耦通信,支持事件驱动、基于兴趣的注册、异步通信以及高度的可扩展性和灵活性。

3.1 认识发布订阅模式

    发布订阅模式是一种消息传递模型,它定义了如何在发布者和订阅者之间传递消息。在这种模式中,发布者(也称为发送者或生产者)生成并发送消息到一个中间层,而不需要知道或关心哪些订阅者(也称为接收者或消费者)会接收这些消息。订阅者则向中间层注册自己的兴趣,以便在相关消息发布时接收它们。

    发布订阅模式具有以下功能:👇

  1. 消息发布:
    🌈 发布者能够生成消息并将其发送到中间层。这些消息可以是事件通知、状态更新、数据变更等。发布者不需要知道订阅者的具体信息,只需将消息发送到中间层即可。

  2. 消息订阅:
    🌈 订阅者能够向中间层注册自己的兴趣,表示它们希望接收特定类型的消息。订阅者可以指定自己感兴趣的消息类型、主题或标签,以便在相关消息发布时得到通知。

  3. 消息分发:
    🌈 中间层负责接收发布者发送的消息,并根据订阅者的兴趣将消息分发给相应的订阅者。这个过程是自动的,订阅者不需要主动轮询或请求消息,而是被动地接收中间层推送的消息。

  4. 解耦:
    🌈 发布订阅模式实现了发布者和订阅者之间的解耦。它们不需要直接相互依赖或了解对方的存在。这种解耦使得系统更加灵活,易于扩展和维护。

  5. 异步通信:
    🌈 发布订阅模式支持异步通信。消息的发送和接收不需要同步进行,发布者可以在任何时间发送消息,而订阅者则可以在稍后的时间异步地接收和处理这些消息。这种异步通信机制有助于提高系统的响应性和吞吐量。

  6. 可扩展性:
    🌈 由于发布者和订阅者之间的解耦以及中间层的存在,发布订阅模式提供了很高的可扩展性。新的发布者或订阅者可以很容易地添加到系统中,而不需要修改现有的代码或配置。此外,系统可以支持多种不同类型的事件和消息,以满足不同的业务需求。

  7. 容错性和可靠性:
    🌈 发布订阅模式可以支持消息的持久化存储和重试机制,以确保消息在传输过程中的可靠性和容错性。即使订阅者暂时不可用或网络出现故障,消息也不会丢失,而是会在适当的时候重新发送给订阅者。

3.2 实现步骤

    发布订阅模式是一种在软件工程中广泛使用的设计模式,它定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题(或事件)。当该主题(或事件)在发布者对象上触发时,所有订阅了该主题的订阅者对象都会自动接收到通知。

    以下是实现发布订阅模式的基本步骤:👇

  1. 确定发布者和订阅者:
    ⌛ 首先,需要明确系统中的哪些对象将充当发布者,哪些对象将充当订阅者。发布者负责产生并发送事件或消息,而订阅者则负责接收并处理这些事件或消息。

  2. 创建事件通道或消息代理:
    ⌛ 这是发布者和订阅者之间的中间层,用于管理事件或消息的传递。发布者将事件发送到这个中间层,而订阅者则从这个中间层接收事件。这个中间层可以是一个事件总线、消息队列、或者任何能够实现发布者和订阅者之间解耦的机制。

  3. 注册订阅者:
    ⌛ 订阅者需要向事件通道或消息代理注册自己的兴趣,即它们希望接收哪些类型的事件或消息。这通常通过调用事件通道或消息代理的注册方法来实现,注册时需要提供订阅者的标识符和它们感兴趣的事件类型。

  4. 发布事件:
    ⌛ 当发布者产生一个事件时,它会将该事件发送到事件通道或消息代理。发布者不需要知道有哪些订阅者正在监听这个事件,也不需要直接与订阅者进行通信。事件的发布通常通过调用事件通道或消息代理的发布方法来实现,发布时需要提供事件的类型和相关的数据。

  5. 分发事件:
    ⌛ 事件通道或消息代理接收到事件后,会根据注册信息将事件分发给所有对该类型事件感兴趣的订阅者。这个过程是自动的,订阅者不需要主动请求或轮询事件。事件通道或消息代理负责确保事件能够正确地分发给所有相关的订阅者。

  6. 处理事件:
    ⌛ 订阅者接收到事件后,会根据自己的业务逻辑对事件进行处理。处理的方式取决于具体的业务需求和订阅者的实现。处理完事件后,订阅者可以选择是否继续监听该类型的事件,或者注销自己的订阅。

    发布订阅模式允许发布者和订阅者之间实现解耦的通信,提高了系统的灵活性和可扩展性。同时,由于事件的分发是自动的,因此也提高了系统的响应速度和吞吐量。

3.3 思考中介者模式

 
    相似的设计模式

 1. 观察者模式(Observer Pattern)

    相似点

    一对多依赖:两者都处理一对多的依赖关系,即一个对象(发布者/主题)改变其状态时,多个对象(订阅者/观察者)会收到通知。
    解耦:两者都实现了对象之间的解耦,允许它们独立地改变和演化。

    易混淆点

    通知机制:发布订阅模式通常依赖于一个中间层(如事件总线)来传递消息,而观察者模式则直接由主题对象通知其观察者。
    异步性:发布订阅模式通常支持异步通信,而观察者模式通常是同步的。
 
 2. 中介者模式(Mediator Pattern)

    相似点

    集中控制:两者都通过一个中间层(发布订阅模式中的事件通道或消息代理,中介者模式中的中介者对象)来集中控制对象间的交互。
    减少依赖:两者都减少了对象之间的直接依赖,使得系统更加灵活和可维护。

    易混淆点

    职责:中介者模式通常负责协调多个对象之间的复杂交互,而发布订阅模式更专注于事件的发布和订阅。
    通信类型:中介者模式通常处理请求/响应类型的通信,而发布订阅模式则更侧重于事件的通知和分发。

 
    容易混淆的设计模式

 1. 消息队列模式(Message Queue Pattern)

    易混淆点

    异步通信:发布订阅模式和消息队列模式都支持异步通信,使得发送者和接收者可以独立地工作。
    中间件:两者都依赖于一个中间件(发布订阅模式中的事件通道或消息代理,消息队列模式中的消息队列)来进行消息的传递。

    区分点

    持久性:消息队列通常支持消息的持久化存储,即使在系统崩溃或重启后也能保证消息的可靠传递。而发布订阅模式则可能只是临时传递消息。
    用途:消息队列通常用于构建分布式系统或微服务架构中的通信机制,而发布订阅模式则更广泛地应用于各种需要解耦通信的场景。
 
 2. 责任链模式(Chain of Responsibility Pattern)

    易混淆点

    多个处理者:在责任链模式和发布订阅模式中,都可能涉及多个对象或组件来处理请求或事件。

    区分点

    处理顺序:责任链模式按照预设的顺序依次将请求传递给链中的对象,直到某个对象处理它或链结束。而发布订阅模式则是同时通知所有订阅者。
    处理结果:在责任链模式中,每个对象都可以决定是否继续传递请求,而在发布订阅模式中,发布者不关心订阅者如何处理事件。

    这些设计模式在某些方面有相似之处,但也存在明显的差异。理解这些相似点和易混淆点有助于更准确地选择和应用设计模式,以满足特定的软件设计需求。
 

四、总结🌟

在这里插入图片描述

4.1 优点💖

    发布订阅模式在松耦合、关注点分离、高伸缩性、高可靠性和异步通信等方面具有显著优点。这使得它在许多复杂和分布式系统中得到广泛应用,成为实现高效、可靠和可扩展通信的重要手段之一。然而,也需要注意到该模式可能引入一些额外的复杂性,如中间件的管理和维护等,因此在应用时需要权衡其优缺点并根据具体场景进行选择。以下是对发布订阅模式优点的详细描述:👇

  1. 松耦合与独立性:
    ❤️ 发布订阅模式的核心优点之一在于它能够将众多需要通信的子系统或组件解耦。这意味着发送者和接收者之间不需要直接了解彼此的存在或实现细节。每个子系统或组件都可以独立地管理自己的逻辑和状态,而不需要关心其他部分的变化。这种松耦合的特性使得系统更加灵活,可以更容易地添加、删除或修改子系统,而不会对整个系统的结构造成大的影响。

  2. 关注点分离:
    ❤️ 在发布订阅模式中,发送者(发布者)的主要职责是发布消息或事件,而接收者(订阅者)则关注于处理这些消息或事件。这样,每个组件都可以专注于其核心功能,而不必担心与其他组件的交互细节。这种关注点分离使得代码更加清晰、易于理解和维护。

  3. 高伸缩性:
    ❤️ 发布订阅模式具有出色的伸缩性。由于发送者和接收者之间是解耦的,因此系统可以轻松地处理大量的并发请求或事件。此外,通过增加更多的订阅者或优化消息传递机制,系统可以进一步扩展其处理能力。这种伸缩性使得发布订阅模式非常适合处理大规模、高并发的应用场景。

  4. 高可靠性:
    ❤️ 发布订阅模式通过异步通信和消息队列等方式,提高了系统的可靠性。即使部分子系统或组件出现故障或下线,也不会影响整个系统的消息传递和事件处理。此外,通过消息持久化、重试机制等技术手段,可以确保消息在传输过程中的可靠性和完整性。这种高可靠性使得系统更加稳定、可依赖。

  5. 异步通信:
    ❤️ 发布订阅模式支持异步通信,这意味着发送者不需要等待接收者处理完消息后再继续执行其他任务。这种异步性使得系统可以更加高效地利用资源,提高响应速度和处理能力。同时,异步通信也减少了系统之间的耦合度,使得系统更加灵活和可扩展。

4.2 缺点

    发布订阅模式虽然具有许多优点,但也存在一些缺点和挑战。在应用该模式时,需要权衡其优缺点,并根据具体场景和需求进行选择。同时,需要采取适当的设计和实现策略来缓解这些缺点带来的问题。例如,可以通过引入消息顺序保证机制、优化消息代理的性能、加强系统的安全性和可靠性等措施来改进发布订阅模式的实现。以下是对发布订阅模式缺点的详细描述:👇

  1. 消息传递的顺序问题:
    💔 在发布订阅模式中,消息的顺序不能得到保证。如果订阅者之间有依赖关系,或者某些操作需要按照特定顺序执行,那么这可能会成为一个问题。因为发布者只是简单地将消息发送给所有订阅者,而不关心它们如何处理或消费这些消息。

  2. 消息代理的性能开销:
    💔 发布订阅模式中通常需要引入一个消息代理或中间件来管理消息的发布和订阅。这可能会带来额外的性能开销,包括内存占用、处理延迟等。特别是在处理大量消息或高并发场景时,这种性能开销可能会更加明显。

  3. 复杂性增加:
    💔 发布订阅模式可能增加系统的复杂性。由于引入了额外的组件和通信机制,系统的架构和设计可能变得更加复杂。此外,多个发布者和订阅者之间的交互也可能导致难以追踪和调试的问题。

  4. 可能的内存泄漏:
    💔 如果订阅者忘记取消订阅,或者由于某些原因无法处理接收到的消息,那么这可能会导致内存泄漏。因为消息代理可能会持续地向这些订阅者发送消息,占用越来越多的内存资源。

  5. 安全性问题:
    💔 发布订阅模式可能面临安全性挑战。由于消息是在发布者和订阅者之间传递的,因此可能存在未经授权的访问、数据泄露或篡改等风险。需要采取适当的安全措施来保护消息的安全性和完整性。

4.3 挑战和局限

    发布订阅模式作为一种流行的软件设计模式,确实为构建松耦合、可扩展和异步通信的系统提供了强大的支持。然而,它并非没有局限和挑战。以下是对发布订阅模式面临的主要挑战和局限的详细描述:👇

    挑战

  1. 消息顺序保证:
    💖 在并发环境中,确保消息按照预期的顺序被处理是一个挑战。发布订阅模式本身不提供消息排序的机制,这可能导致订阅者接收到乱序的消息。

  2. 消息过滤和路由:
    🤩 当系统中有大量的发布者和订阅者时,有效地过滤和路由消息变得复杂。需要设计高效的策略来确保消息能够准确地传递给感兴趣的订阅者,同时避免不必要的消息传递。

  3. 系统监控和调试:
    🌈 由于发布订阅模式的异步和分布式特性,监控和调试变得困难。跟踪消息的流动、识别和处理故障点需要额外的工具和日志记录策略。

  4. 性能优化:
    🚀 在高负载下,消息代理或事件总线可能成为性能瓶颈。需要优化这些组件以处理大量的消息流,并确保系统的响应时间和吞吐量满足需求。

  5. 安全性保障:
    🎭 确保消息在传输和存储过程中的安全性是一个重要挑战。需要实施加密、身份验证和访问控制等安全措施来保护敏感数据免受未经授权的访问和篡改。

    局限

  1. 不适合实时性要求极高的场景:
    😂 由于消息传递的异步性和可能的延迟,发布订阅模式可能不适合那些对实时性要求极高的应用,如金融交易系统或实时控制系统。

  2. 额外的管理和维护开销:
    😢 引入消息代理或事件总线增加了系统的复杂性,需要额外的管理和维护工作。这包括配置、部署、监控和升级中间件等任务。

  3. 可能的消息丢失:
    🧐 在分布式环境中,网络故障、进程崩溃或存储故障可能导致消息丢失。虽然可以通过持久化、重试和确认机制来减少这种风险,但完全避免消息丢失是困难的。

  4. 数据一致性挑战:
    🤔 在分布式系统中使用发布订阅模式时,确保数据的一致性和完整性是一个挑战。特别是在处理跨多个订阅者的状态时,需要仔细设计更新策略和同步机制。

  5. 订阅者依赖管理:
    😅 当订阅者之间存在依赖关系时,管理这些依赖关系可能变得复杂。需要确保订阅者以正确的顺序接收到消息,并处理可能的依赖冲突。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1523084.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UE5数字孪生系列笔记(二)

智慧城市数字孪生系统 制作流云动画效果 首先添加一个图像在需要添加流云效果的位置 添加动画效果让其旋转 这个动画效果是程序开始就要进行的&#xff0c;所以要在EventConstruct中就可以启动这个动画效果 添加一个一样的图像在这里&#xff0c;效果是从此处进行放大消散 添…

python之自动化(django)

1、安装 我用的是pip install Django 在命令行中安装 然后django-admin startproject autotext&#xff08;在命令行中&#xff09; 这句话是创建一个django 项目 然后切换到你所创建项目的目录下 输入&#xff1a; python manage.py runserver 当你出现以下错误时 You…

高光谱数据应用于植被监测与分析与数据获

1. 常用高光谱数据 (1) 航空成像光谱仪系统 国内系统&#xff1a;MAIS、OMIS-1、OMIS-2、PHI、WHI、LASIS 国外系统&#xff1a;AIS、AVIRIS、TRWIS、GERIS、HYDICEAISA、DAIS、CASI、HYMAP (2) 航天成像光谱仪 Hyperion/EO-1 环境与减灾小卫星星座&#xff08;HJ-1B&…

【jeecgboot】微服务实战LISM

目录 一、服务解决方案-Spring Cloud Alibaba1.1选用原因&#xff08;基于Spring Cloud Alibaba的试用场景&#xff09;1.2 核心组件使用前期规划 部署 nacos部署 mino使用JavaFreemarker模板引擎&#xff0c;根据XML模板文件生成Word文档使用JavaFlowable 工作流引擎前端 -vue…

【Flink SQL】Flink SQL 基础概念(三):SQL 动态表 连续查询

《Flink SQL 基础概念》系列&#xff0c;共包含以下 5 篇文章&#xff1a; Flink SQL 基础概念&#xff08;一&#xff09;&#xff1a;SQL & Table 运行环境、基本概念及常用 APIFlink SQL 基础概念&#xff08;二&#xff09;&#xff1a;数据类型Flink SQL 基础概念&am…

OpenCV系列文章目录(持续更新中......)

引言&#xff1a; OpenCV是一个开源的计算机视觉库&#xff0c;由英特尔公司开发并开源的一组跨平台的C函数和少量的C函数组成&#xff0c;用于实时图像处理、计算机视觉和机器学习等应用领域。OpenCV可以在包括Windows、Linux、macOS等各种操作系统平台上使用&#xff0c;具…

代码随想录算法训练营三刷day25 | 回溯 之 216.组合总和III 17.电话号码的字母组合

三刷day25 216.组合总和III剪枝 17.电话号码的字母组合 216.组合总和III 题目链接 解题思路&#xff1a; 选取过程如图&#xff1a; 图中&#xff0c;可以看出&#xff0c;只有最后取到集合&#xff08;1&#xff0c;3&#xff09;和为4 符合条件。 递归三部曲 确定递归函数参…

【C++】类和对象终章

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 一、初始化列表1.1 初始化列表的形式1.2 初始化列表的注意事项 二、explicit关键…

【STM32定时器(一)内部时钟定时与外部时钟 TIM小总结】

STM32 TIM详解 TIM介绍定时器类型基本定时器通用定时器高级定时器常用名词时序图预分频时序计数器时序图 定时器中断配置图定时器定时 代码调试代码案例1代码案例2 TIM介绍 定时器&#xff08;Timer&#xff09;是微控制器中的一个重要模块&#xff0c;用于生成定时和延时信号…

Linux/Ubuntu/Debian从控制台启动程序隐藏终端窗口

如果你想从终端运行应用程序但隐藏终端窗口. 你可以这样做&#xff1a; 在后台运行&#xff1a; 你只需在命令末尾添加一个与号 (&) 即可在后台运行它。 例如&#xff1a; your_command &将 your_command 替换为你要运行的命令。 这将在后台启动该命令&#xff0c…

THM学习笔记—Simple CTF

nmap扫描&#xff0c;发现2222端口很奇怪啊&#xff0c;重新换一种方式扫描2222端口 发现是ssh 先用ftp试试&#xff0c;尝试匿名登录 下载所有文件 发现只有一个ForMitch.txt&#xff0c;告诉我们其账号密码为弱密码&#xff0c;我们猜测Mitch为其用户名&#xff0c;尝试暴力…

python 爬取人民新闻

基础信息获取&#xff1a; 要闻url&#xff1a;https://www.gov.cn/yaowen/liebiao/home.htm 下一页的url&#xff1a;https://www.gov.cn/yaowen/liebiao/home_1.htm 基础代码&#xff1a; import re import openpyxl import requests from lxml import etree import osdef …

【Java】图书管理系统,完整版+源代码!!!

1. 图书管理系统菜单 1.1 管理员菜单 查找图书新增图书删除图书显示图书退出系统 1.2普通用户菜单 查找图书借阅图书归还图书退出系统 2.基本框架的实现 首先我们要建立一个新的文件,在文件内建立三个包&#xff0c;分别命名为user(用户)、book&#xff08;图书&#xff…

基于FPGA的图像锐化算法(USM)设计

免费获取源码请关注微信号《FPGA学习笔记册》&#xff01; 1.图像锐化算法说明 图像锐化算法在实际的图像处理应用很广泛&#xff0c;例如&#xff1a;医学成像、工业检测和军事领域等&#xff1b;它的作用就是将模糊的图像变的更加清晰。常用的图像锐化算法有拉普拉斯算子、s…

【全开源】JAVA情侣扭蛋机情侣游戏系统源码支持微信小程序+微信公众号+H5

一、功能介绍 会员功能、情侣扭蛋 收到的券、送出的券 合伙代理、意见反馈 我们技术使用JAVA后台服务 前后端分离 springbootmybatisplusmysql 用户端 uniapp&#xff08;vue语法&#xff09;管理后台 vueelementUi 适配小程序H5公众号&#xff0c;一套源码&#xff0c;无…

LeetCode 2684.矩阵中移动的最大次数:一列一列处理,只记能到哪行(BFS)

【LetMeFly】2684.矩阵中移动的最大次数&#xff1a;一列一列处理&#xff0c;只记能到哪行(BFS) 力扣题目链接&#xff1a;https://leetcode.cn/problems/maximum-number-of-moves-in-a-grid/ 给你一个下标从 0 开始、大小为 m x n 的矩阵 grid &#xff0c;矩阵由若干 正 整…

VsCode 配置go开发环境之下载go tools

ctrl shift P 选择 go install/update tools&#xff0c;下载go tools 报错&#xff0c; 提升dial err。 将GOPROXY 和 GOSUMDB 按照如下配置&#xff0c;重启IDE即可成功下载 set GOPROXYhttps://goproxy.cn set GOSUMDBoff

oops-framework框架 之 启动流程(三)

引擎&#xff1a; CocosCreator 3.8.0 环境&#xff1a; Mac Gitee: oops-game-kit 回顾 上篇博客中我们通过 oops-game-kit 模版构建了基础的项目&#xff0c;另外讲解了下assets目录结构和游戏配置文件的基本使用相关&#xff0c;详情内容可参考&#xff1a; oops-framewo…

「黄钊的AI日报·第三季」正式发布!

每天5条AI内容点&#xff1a;不是新闻汇总&#xff0c;而是站在11年AI产品经理的视角&#xff0c;将原AI信息中的干货认知&#xff0c;提炼成我自己的文字、展示“what I see”。 做社群“AI产品经理大本营”6年以来&#xff0c;我都是在非常用心的输出AI干货&#xff1b;这份“…

html5使用Websocket

html5使用Websocket 前言1、html5中的websocket2、创建一个 WebSocket 对象3、监听 WebSocket 连接事件4、监听 WebSocket 收到消息事件5、监听 WebSocket 关闭事件6、 监听 WebSocket 出错事件7、发送消息8、整体代码 前言 在即时通讯的交互方式中websocket是一个很使用的方式…