在软件开发中,观察者模式是一种常用的设计模式,它定义了对象之间的一对多依赖关系,当一个对象状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。然而,当一个对象有多个部分可以同时变化时,这种“一对多”的关系可能会引起混淆,让人误以为是“多对多”的关系。本文将通过一个车辆跟踪系统的例子,帮助澄清这种混淆。
假设正在开发一个车辆跟踪系统。这个系统会在车辆启动/停止、空调开启/关闭或GPS信号丢失时,向用户发送短信/电子邮件。无论车主是否在车内,只要有人驾驶车辆(可能是小偷),车主都会收到通知。车辆上安装的设备会向服务器发送数据包,服务解析该数据包,与前一个数据包进行比较,检测发动机/空调状态的变化(开启/关闭)。
这里,数据包可以单独或同时改变其发动机、空调或GPS状态。假设用户第一次打开空调,天空晴朗,可以完全接收GPS信号。所以,所有状态位都是1。当他停车时,发动机和空调状态变为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。
从接口开始。Java代码如下:
public interface IPacketChange {
public void notifyAlert(PacketV3 packet);
}
在AlertService类中,匿名实现接口并添加到相应的列表中。Java代码如下:
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比较当前数据包与前一个数据包,并分别检测变化。Java代码如下:
if (packet.getEngineStatus() != previousPacket.getEngineStatus()) {
for (IPacketChange iPacketChange : onEngineStatusChange) {
iPacketChange.notifyAlert(packet);
}
}
if (packet.getAcStatus() != previousPacket.getAcStatus()) {
for (IPacketChange iPacketChange : onAcStatusChange) {
iPacketChange.notifyAlert(packet);
}
}
这就是接口在这里的作用。不需要担心那些sendSMS()和sendEmail()方法的列表/指针。对于每个状态变化,相应的警报服务将从相应的列表中得到通知。在列表中,有接口的匿名类实现。sendSms()和sendEmail()在那个匿名类中调用。