Java 多线程应用程序中的观察者设计模式





5.00/5 (2投票s)
一篇关于如何在需要函数指针的多线程 Java 应用程序中实现观察者设计模式的文章。
引言
从观察者设计模式的概念中,我们知道观察者向主题注册。一旦收到主题的通知,观察者就会调用主题并获取已更改的数据。
此外,Subject
和 Observer
之间存在 一对多
关系。但当主题有多个部分可以同时更改时,就会出现混淆。那时,“一对多
”关系似乎变成了“多对多
”关系,这会在实现过程中引起混淆。
本文将帮助您消除这种混淆。
现实生活场景
假设我们正在开发一个车辆追踪系统。当汽车启动/停止、空调打开/关闭或 GPS 丢失时,该系统会向用户发送短信/电子邮件。
无论车主是否在车内,当其他人驾驶汽车时(可能是小偷 ;)),他都会收到通知。
附加在汽车上的设备将一个数据包发送到服务器,服务解析该数据包,与前一个数据包进行比较,并检测到发动机/空调状态的变化(开/关)。
在这里,数据包的发动机、空调或 GPS 状态可以单独更改,也可以同时更改。
假设用户第一次打开空调,天空晴朗,GPS 信号满格。
所以,所有状态位都是 1。当他停车时,发动机和空调的状态同时变为 0。此时,发动机和空调的状态为 0,但 GPS 状态仍为 1。
混淆之处
已注册的观察者将收到此状态更改场景的通知,但实际更改了什么状态?是多个状态改变还是单个状态?
如果多个状态改变,多个观察者将处理一个数据包的多个更改。
(发动机和空调状态同时改变。电子邮件和短信服务都必须为这两种状态更改发送两封单独的电子邮件/短信)。
这足以导致“多对多
”的误解,这违反了观察者模式的原则。
实际上,当您单独检测更改并从单个指针运行警报服务方法时,它仍然是“一对多
”关系。
开发人员必须将所有 AlertService
方法列出在某个地方,当任何状态发生变化时,他将遍历该列表。
对于每项更改,遍历包含这些方法的列表。
现在的问题是如何做到?Java 不支持函数指针。如何将这些方法列在同一个地方?
答案:通过使用接口
项目架构
每当有一辆新车进来时,它将通过一个新线程连接。
- 主类调用
AlertService
类的register()
方法。 - 主类启动数据包接收器线程。
- 项目接收器接收新车并启动
PacketAnalyzer
。 PacketAnalyzer
从套接字读取数据并解析接收到的数据包。- 然后它启动
PacketProcessor
,该处理器检测数据包中的变化并调用观察者。
所有警报服务都将注册到我们的主题(实际上是 List
实现),观察数据包的变化,并在数据包发生变化时触发事件(遍历列表)。
如何实施
让我们通过一个编码示例来进一步探讨。
数据包结构:HardwareId
, Latitude
, Longitude
, Engine_Status
, Ac_Status
, Gps_Status
示例数据包:000001
, 23.819624
, 090.366783
, 1
, 0
, 1
这里,
- 发动机状态 = 开
- 空调状态 = 关
- 有 GPS = true
从接口开始。
public interface IPacketChange {
public void notifyAlert(PacketV3 packet);
}
在 AlertService
类中,我们匿名实现 interface
并将其添加到相应的列表中。
PacketProcessor.onAcStatusChange.add(new IPacketChange() {
@Override
public void notifyAlert(PacketV3 packet) {
sendSMS(packet,"ac");
sendEmail(packet,"ac");
}
});
PacketProcessor.onEngineStatusChange.add(new IPacketChange() {
@Override
public void notifyAlert(PacketV3 packet) {
sendSMS(packet,"engine");
sendEmail(packet,"engine");
}
});
这里,onAcStatusChange
和 onEngineStatusChange
是 PacketProcessor
中的两个 ArrayList
。
为什么这两个列表在 PacketProcessor
中而不是在 AlertService
中?
你很快就会得到答案。
服务注册后,PacketAcceptor
和 PacketAnalyzer
从服务器套接字创建套接字并接受来自汽车的数据包。
我跳过这部分,因为这是普通的套接字编程,您可以在这里下载源代码。
如果对此部分有任何疑问,请在评论区告知我。
在 PacketAnalyzer
分析数据后,它启动 PacketProcessor
。PacketProcessor
将当前数据包与之前的数据包进行比较,并单独检测变化。
if(packet.getEngineStatus() != previousPacket.getEngineStatus()){
for(IPacketChange iPacketChange : onEngineStatusChange){
iPacketChange.notifyAlert(packet);
}
}
if(packet.getAcStatus() != previousPacket.getAcStatus()){
for(IPacketChange iPacketChange : onAcStatusChange){
iPacketChange.notifyAlert(packet);
}
}
这就是 Interface
如何成为这里的救星。我们不再需要担心那些 sendSMS()
和 sendEmail()
方法的列表/指针。
对于每种状态变化,将从相应的列表中通知单独的警报服务。
而在列表内部,我们有 Interface
的匿名类实现。sendSms()
和 sendEmail()
在该匿名类中被调用。
为什么这个架构模型有用
当我们引入一个新的警报服务(这次是推送通知)时,我们只需要在 AlertService
中创建一个新的方法 sendPushNotification()
并在匿名类中调用它。
因此,所有更改都在一个类中,无需触碰 PacketProcessor
或任何其他服务类中的任何代码。
这里的主要优势是模块化。
附注:我使用了多线程,以便每辆车通过一个单独的线程进行通信,一个线程不会影响另一个线程。Sender 和 Main Project 都已在此处上传。请先运行 Main Project,然后使用 Sender Project 发送数据。