独立构件风格
构件之间是互相独立的,不存在显式的调用关系,而是通过某个事件触发、异步的方式来执行,代表的风格有:
- 进程通信
- 事件驱动系统(隐式调用)
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>