- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
导读
本文以 java-design-patterns 仓库中data-bus模块(data-bus/README.md)为核心,系统讲解 Data Bus(数据总线,又称 Event Bus / Message Bus)设计模式。该模式为系统各组件提供一个集中式通信通道,组件之间无需直接互相引用即可交换数据,从而获得低耦合、易扩展、易维护的架构收益。读完本文,你将掌握 Data Bus 模式的核心思想、Java 实现结构、消息按类型选择性分发的机制,以及它在事件驱动、微服务等场景下的适用边界与取舍。
Data Bus 模式是什么
Data Bus 设计模式的目标是:提供一个集中式通信通道,让系统中各种组件无需直接连接即可交换数据,从而促进松耦合,并提升系统的可扩展性与可维护性。
- 别名:Event Bus(事件总线)、Message Bus(消息总线)。
- 一句话概括:Data Bus 是一种"根据被传递的消息或事件类型来连接应用组件"的设计模式——组件只按类型接收自己关心的消息,互相之间不产生直接依赖。
现实类比:机场广播系统
大型机场中有航空公司、乘客、行李搬运工、安检人员等多类实体,它们都需要互通信息。若让每个实体直接与其他实体通信,关系网会极其复杂。机场实际采用的做法是:使用一套**集中式广播系统(即 Data Bus)**统一发布航班信息、安检警报等关键更新,各实体只监听与自己相关的消息。这样既解耦了通信过程,又能无破坏地接入新实体,与 Data Bus 在 Java 中的定位一致。
消息分发流程
程序化示例:在线活动报名应用
假设你有一个支持在线预订和参与活动的应用,希望把活动广告这类通知发送给社区普通成员,但不发给活动管理员/组织者;而管理员则需要收到"新广告已发送给全体成员"这类时机通知。Data Bus 让两类成员各自只接受特定类型的消息,双方互不知晓对方类别的存在,只知道"正在传递的消息类型"。
仓库中该模块源码位于>public interface Member extends Consumer<DataType> { void accept(DataType event); }
2. 定义事件数据类型 DataType 与 AbstractDataType
通过总线发布的数据必须实现 DataType 接口,它承载"事件在哪条总线上发送"的回指引用:
public interface DataType { DataBus getDataBus(); void setDataBus(DataBus dataBus); }AbstractDataType 使用 Lombok 的@Getter/@Setter提供默认实现,具体事件类型只需继承它并携带自己的业务字段即可:
@Getter @Setter public class AbstractDataType implements DataType { private DataBus dataBus; }仓库内置了三种事件类型(位于>public class DataBus { private static final DataBus INSTANCE = new DataBus(); private final Set<Member> listeners = new HashSet<>(); public static DataBus getInstance() { return INSTANCE; } public void subscribe(final Member member) { this.listeners.add(member); } public void unsubscribe(final Member member) { this.listeners.remove(member); } public void publish(final DataType event) { event.setDataBus(this); listeners.forEach( listener -> listener.accept(event)); } }
要点说明:
- 单例:
DataBus.getInstance()返回全局唯一实例(源码注释明确标注该实现使用 Singleton)。 publish的第一步是event.setDataBus(this),把事件回指到当前总线——这正是StatusMember能在收到事件后"反向通过总线再发消息"的前提(见下文)。- 同步语义:从
App.java的类注释可知,这是一个同步数据总线——publish方法在所有成员都接收并处理完事件之后才返回。 - 多对多:任意数量的发布者可以向总线发数据,任意数量的成员接收数据;所有成员收到的是同一份数据,各成员收到数据的先后顺序是实现的细节(
HashSet不保证顺序)。
4. 按类型选择性接收的成员实现
普通成员MessageCollectorMember 只处理MessageData类型的消息,其他类型直接忽略:
public class MessageCollectorMember implements Member { private final String name; private final List<String> messages = new ArrayList<>(); public MessageCollectorMember(String name) { this.name = name; } @Override public void accept(final DataType data) { if (data instanceof MessageData) { handleEvent((MessageData) data); } } }handleEvent内部通过LOGGER.info("{} sees message {}", name, data.getMessage())打印日志并把消息存入messages列表;getMessages()返回不可变副本List.copyOf(messages),防止外部修改内部状态。
管理员/组织者StatusMember 则处理StartingData与StoppingData两类事件:
public class StatusMember implements Member { private final int id; private LocalDateTime started; private LocalDateTime stopped; public StatusMember(int id) { this.id = id; } @Override public void accept(final DataType data) { if (data instanceof StartingData) { handleEvent((StartingData) data); } else if (data instanceof StoppingData) { handleEvent((StoppingData) data); } } }值得注意的源码细节:当StatusMember收到StoppingData(应用停止)时,它会借助事件上回指的getDataBus()反向发布一条告别消息:
private void handleEvent(StoppingData data) { stopped = data.getWhen(); LOGGER.info("Receiver {} sees application stopping at {}", id, stopped); LOGGER.info("Receiver {} sending goodbye message", id); data.getDataBus().publish(MessageData.of(String.format("Goodbye cruel world from #%d!", id))); }这说明 Data Bus 中的成员既是消费者,也可以作为发布者,成员之间通过总线形成"环形消息流",而彼此仍不知道对方的存在。
5. 组合演示 App
App.java 串联了完整流程:订阅 → 发布启动事件 → 发布消息 → 中途订阅新成员 → 退订旧成员 → 发布停止事件:
class App { public static void main(String[] args) { final var bus = DataBus.getInstance(); bus.subscribe(new StatusMember(1)); bus.subscribe(new StatusMember(2)); final var foo = new MessageCollectorMember("Foo"); final var bar = new MessageCollectorMember("Bar"); bus.subscribe(foo); bus.publish(StartingData.of(LocalDateTime.now())); } }当总线发布StartingData时,输出如下:
02:33:57.627 [main] INFO com.iluwatar.databus.members.StatusMember - Receiver 2 sees application started at 2022-10-26T02:33:57.613529100 02:33:57.633 [main] INFO com.iluwatar.databus.members.StatusMember - Receiver 1 sees application started at 2022-10-26T02:33:57.613529100如上所示,MessageCollectorMember只接受MessageData,因此看不到StartingData/StoppingData这两类只有StatusMember(管理员/组织者)可见的事件。这种按消息类型的选择性分发,恰好阻止了普通成员收到管理类通知。
如果沿 App 的完整执行序列继续运行(发布MessageData.of("Only Foo should see this")、订阅 bar、发布MessageData.of("Foo and Bar should see this")、退订 foo、发布MessageData.of("Only Bar should see this")、发布StoppingData),可以看到:普通成员按订阅/退订状态动态接收消息,而StatusMember收到停止事件后又向总线回发告别消息。
6. 测试印证
DataBusTest.java 用 Mockito 直接验证了总线契约:
publishedEventIsReceivedBySubscribedMember:订阅后发布事件,then(member).should().accept(event)断言订阅者收到事件;publishedEventIsNotReceivedByMemberAfterUnsubscribing:退订后发布事件,断言成员不再收到。
MessageCollectorMemberTest 与 StatusMemberTest 则分别验证了各成员只处理自己关心的消息类型,测试与源码共同构成了"类型选择性分发"行为的证据链。
何时使用 Data Bus 模式
符合以下情况时,Data Bus 是合适的选择:
- 多个组件需要共享数据或事件,但不希望直接耦合;
- 复杂的事件驱动系统,其中信息流动随运行动态变化;
- 分布式系统,组件可能部署在不同环境中;
- 微服务架构下的服务间通信。
Data Bus 模式的真实应用场景
- 大规模应用中的事件处理系统;
- 微服务架构中的服务间通信;
- 实时数据处理系统(如股票交易平台);
- 类似 Spring 的框架生态,特别是其application event(应用事件)机制。
优点与权衡
优点(Benefits)
- 松耦合:组件交互不产生直接依赖;
- 灵活:新增订阅者或发布者无需影响既有组件;
- 可扩展:各组件可独立扩展;
- 可复用:总线和组件可在不同系统中复用。
代价(Trade-offs)
- 复杂度:引入总线会给系统架构增加一层复杂度;
- 性能开销:额外的通信层可能引入延迟;
- 调试困难:在事件繁多的系统中,追踪数据流会比较困难。
与其他设计模式的关系
- Mediator(中介者):同样促进组件间通信,但 Mediator 把控制逻辑集中起来;Data Bus 则把控制分散到总线与各成员之间。
- Observer(观察者):Data Bus 在本质上是发布-订阅机制,与 Observer 通知多个对象状态变化的方式相似。从 App.java 的注释也可以看到,Data Bus 与观察者模式相似——成员在总线上注册后接收发布的每份数据,并可自行决定是否响应某条消息。
- Publish/Subscribe(发布-订阅):Data Bus 常以发布-订阅机制实现——发布者向总线投递消息而无需了解订阅者是谁。
参考资料
本文内容以>赞
- 示例工程
- 教程
【免费下载链接】java-design-patterns
Design patterns implemented in Java
相关推荐
macOS新手向:5分钟装好notepad--跨平台文本编辑器:中文编码与双窗口对比全带上
macOS新手向:5分钟装好notepad 跨平台文本编辑器:中文编码与双窗口对比全带上 你习惯用系统自带TextEdit记笔记,一碰到 .c 文件或GBK编码
示例工程教程Truffle Dashboard 消息总线通用类型包 @truffle/dashboard-message-bus-common 详解
Truffle Dashboard 消息总线通用类型包 @truffle/dashboard message bus common 详解 导读 @truffle
区块链开发工具Web3Java 设计模式:Acyclic Visitor(无环访问者)模式解析与源码实战 —— 基于 java-design-patterns 仓库 acyclic-visitor 模块
Java 设计模式:Acyclic Visitor(无环访问者)模式解析与源码实战 —— 基于 java design patterns 仓库 acyclic
示例工程教程