系统架构设计师--软件架构风格--独立构件风格--发布-订阅模式(Pub/Sub)例子

独立构件风格

构件之间是互相独立的,不存在显式的调用关系,而是通过某个事件触发、异步的方式来执行,代表的风格有:

  • 进程通信
  • 事件驱动系统(隐式调用)

A. 进程通信风格

  • 核心概念 :
    • 构件:独立的过程(通常是命名过程)。
    • 连接件:消息传递。
  • 通信方式 :
    • 支持点到点通信。
    • 支持同步或异步方式。
    • 支持远程过程调用 (RPC)。

B. 事件驱动系统(隐式调用)

  • 核心思想:

    • 构件不直接调用过程,而是触发或广播事件。
    • 其他构件中的过程需先在事件中注册。
    • 当事件被触发时,系统自动调用所有注册过的过程。
  • 架构组成:

    • 构件:既可以是模块(过程),也可以是事件的集合。
    • 调用方式:既支持通用方式调用,也支持在系统事件中注册调用。
  • 例子:发布-订阅模式(Pub/Sub)

  • 总结:优缺点分析

    • 主要优点
      • 强大的复用支持:为软件复用提供了强有力的基础。
      • 易于维护与演化:方便构件的维护和后续演化。
    • 主要缺点
      • 失去控制权:构件放弃了对自己系统计算过程的控制权(因为是被动触发)。

一句话总结:

这一页主要讲的是独立构件风格 ,重点区分了进程通信 (直接消息传递)和事件驱动(隐式调用/注册触发)两种模式,并指出了事件驱动模式虽然利于复用和维护,但牺牲了对计算流程的直接控制。

发布-订阅模式(Pub/Sub)例子

例子代码:static_example_code/event-pub-sub.html

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>发布-订阅模式演示</title>
        <style>
            body {
                font-family: system-ui, sans-serif;
                max-width: 560px;
                margin: 2rem auto;
                padding: 0 1rem;
                color: #1e293b;
            }
            input, button {
                font: inherit;
                padding: 0.5rem 0.9rem;
                border-radius: 8px;
                border: 1px solid #cbd5e1;
            }
            button {
                background: #3b82f6;
                color: #fff;
                border: none;
                cursor: pointer;
            }
            .subscriber {
                padding: 0.6rem 1rem;
                margin: 0.5rem 0;
                background: #f1f5f9;
                border-radius: 8px;
            }
        </style>
    </head>
    <body>
        <h1>📡 发布-订阅模式 (Pub/Sub)</h1>
        <p>发布者只管发事件,完全不认识订阅者;事件中心负责把消息广播给所有订阅者。</p>

        <input id="msg" value="股票上涨 5%" />
        <button id="publish">发布事件</button>

        <h2>订阅者</h2>
        <div id="subscribers"></div>
        <script>
            /**
             * 事件中心:发布-订阅(Pub/Sub)模式的实现,只负责三件事:
             * - 登记回调(订阅):`on(name, cb)`
             * - 删除回调(退订):`off(name, cb)`
             * - 广播事件(发布):`emit(name, data)`
             *
             * 内部用 `Map<事件名, Set<回调函数>>` 维护订阅关系,
             * 发布者只调用 `emit` 即可通知所有订阅者,彼此互不感知。
             */
            class EventEmitter {
                constructor() {
                    // { 事件名: Set<回调函数>, ... }
                    this.events = new Map();
                }
                /**
                 * 订阅一个事件,将回调函数登记到对应的事件名集合上。
                 * @param {string} name - 事件名。
                 * @param {(data: any) => void} cb - 事件触发时执行的回调函数,接收事件数据。
                 * @returns {() => void} 退订函数:调用即可移除本次注册的回调。
                 */
                on(name, cb) {
                    if (!this.events.has(name))
                        this.events.set(name, new Set());
                    this.events.get(name).add(cb);
                    return () => this.off(name, cb); // 返回退订函数
                }
                /**
                 * 退订一个事件:从对应事件名集合中移除回调,若集合为空则删除该事件名。
                 * @param {string} name - 事件名。
                 * @param {(data: any) => void} cb - 之前通过 on 注册的回调函数。
                 * @returns {void}
                 */
                off(name, cb) {
                    const set = this.events.get(name);
                    if (set) {
                        set.delete(cb);
                        if (set.size === 0) this.events.delete(name);
                    }
                }
                /**
                 * 发布一个事件:将事件数据广播给所有已订阅该事件的回调函数。
                 * @param {string} name - 事件名。
                 * @param {any} data - 要传递给回调的事件数据。
                 * @returns {void}
                 */
                emit(name, data) {
                    const set = this.events.get(name);
                    // 复制一份,防止遍历中退订出错
                    if (set) [...set].forEach((cb) => cb(data));
                }
            }
            // 每个订阅者:订阅事件 + 只渲染自己这一块
            /**
             * 注册一个订阅者:创建一个 DOM 容器,并订阅指定事件,收到事件后只更新自己的那一块区域。
             * @param {string} name - 订阅者名称,显示在渲染区域前缀(如「📱 手机端」)。
             * @param {(data: any) => string} format - 格式化函数,接收事件数据并返回要显示的字符串。
             * @returns {void}
             */
            function subscribe(name, format) {

                const el = document.createElement("div");
                el.className = "subscriber";
                el.textContent = `${name}:等待事件...`;

                document.getElementById("subscribers").appendChild(el);

                // 订阅事件
                bus.on(EVENT, (data) => {
                    el.textContent = `${name}:${format(data)}`;
                });
            }

            const bus = new EventEmitter();  // 实例化事件中心
            const EVENT = "priceUpdate";     // 事件名

            // 订阅
            subscribe("📱 手机端", (d) => `收到 ${d}`);
            subscribe("💻 桌面端", (d) => `事件 ${d}`);
            subscribe("🤖 量化机器人", (d) => `消息 ${d}`);

            // 发布者:只管发,不认识任何订阅者
            document.getElementById("publish").onclick = () => {
                const data = document.getElementById("msg").value;
                bus.emit(EVENT, data);
            };
        </script>
    </body>
</html>
相关推荐
ndsc_d8 天前
2026年有哪些好用的AI UI设计工具?主流工具功能和适用场景对比
前端·人工智能·ui·ai·设计师·ai ui·ai ui工具
languageStudents20 天前
设计师该装的 5 个 Skill:把设计标准写成说明书
设计师·skill
舒一笑5 个月前
零后端、零数据库——我做了一个让 10000+ 人成功告白的开源工具
后端·产品·设计师
小阿鑫6 个月前
设计圈真的要变天了:ChatGPT Image 2 不只是会生图了
chatgpt·aigc·设计师·设计
程序员海军6 个月前
设计圈真的要变天了:ChatGPT Image 2 不只是会生图了
aigc·设计师·交互设计
top_designer8 个月前
Magnific:老旧 UI 糊成马?720p 截图重铸 4K 界面
前端·游戏·ui·prompt·aigc·设计师·游戏策划
da_vinci_x8 个月前
Luma:手绘特效肝出血?“关键帧补全流”,两张图生成丝滑序列帧
游戏·3d·aigc·设计师·特效·游戏策划·游戏美术
reddingtons8 个月前
Scenario: SLG 地图铺到吐?搭建“轴测流水线”,量产建筑不重样
游戏·3d·prompt·aigc·设计师·游戏美术·slg
reddingtons8 个月前
Magnific AI:拒绝“马赛克”?AI 幻觉重绘流,拯救 1024px 废片
图像处理·人工智能·设计模式·新媒体运营·aigc·设计师·教育电商