Java 当订阅者需要主消息类型的子类时,观察者模式会失败吗?
我在使用观察者模式风格的解决方案设计用于发送不同类型消息的干净解决方案时遇到问题 我有一个客户端应用程序通过tcp套接字连接到服务器(我无法更改)。我可以发送和接收json编码的消息,这些消息将始终包含一个“msg”参数,该参数定义消息的类型。还请注意,我可以接收发送给多个客户端的消息,这些消息不是我自己的客户端请求的(例如,如果有人发送聊天消息) 例如: 在连接时,我收到Java 当订阅者需要主消息类型的子类时,观察者模式会失败吗?,java,oop,design-patterns,Java,Oop,Design Patterns,我在使用观察者模式风格的解决方案设计用于发送不同类型消息的干净解决方案时遇到问题 我有一个客户端应用程序通过tcp套接字连接到服务器(我无法更改)。我可以发送和接收json编码的消息,这些消息将始终包含一个“msg”参数,该参数定义消息的类型。还请注意,我可以接收发送给多个客户端的消息,这些消息不是我自己的客户端请求的(例如,如果有人发送聊天消息) 例如: 在连接时,我收到{“msg”:“ServerInfo”,“version”:“1.0a”} 发送{“msg”:“Ping”}回复{“msg”
{“msg”:“ServerInfo”,“version”:“1.0a”}
发送{“msg”:“Ping”}
回复{“msg”:“Ping”,“time”:1381358623}
我可以随时收到{“msg”:“Chat”,“from”:“Person”,“text”:“Hello everyone”}
例如,有些消息更复杂,可能有嵌套对象
{
"msg":"SampleData",
"people":[{
"name":"Joe"
"age":25
},{
"name":"Bob",
"age":30
}]
}
有几十种不同类型的消息,所有消息都具有不同数量和类型的字段
我目前有一个类,负责监听套接字并使用Gson将所有消息解析为一个“BasicMessage”类,该类只有“msg”参数。我有一个映射,将所有消息类型字符串映射到它们各自的类。一旦我有了“msg”参数表,我就可以查找我需要用Gson将其反序列化的类,然后这样做。现在我有了一个正确类的实例,但这就是我的设计开始崩溃的地方
我希望其他各种类都能够只订阅几种类型的消息。问题是,我似乎找不到一种方法来做到这一点,而不需要在每一个客户机上都安装一组instanceof或重新评估所有内容
我最初的想法是使用如下参数化接口:
public interface MessageListener<T> {
public void onReceivedMessage(T message);
}
公共接口MessageListener{
接收到的消息(T消息)公开无效;
}
然后在反序列化消息的类中,我有一个列表
,其中Message是其他消息继承自的抽象类。然后我遇到了类型擦除问题,MessageListener
没有继承自MessageListener
,因此我没有办法将客户端添加到一个简单的列表中。似乎我必须为每种类型的信息列出一个列表,这也不理想。这种设计的另一个问题是,它会限制我在一个需要多个消息侦听器的类中只为不同的消息侦听器使用匿名内部类,因为即使使用不同的类对同一接口进行参数化,也无法实现两次
在这种情况下,我是否可以使用更好的模式?我觉得我可能需要用反思来让它“整洁”地工作。理想情况下,如果我想添加另一种消息类型,我希望它只需要添加要反序列化的类,或者从它的msg字符串到该类的映射,然后能够开始在另一个类中为该类型的消息添加侦听器
提前谢谢 这里有一种可能性(但请注意,我还没有测试过)。不幸的是,它需要在BasicMessage
的每个子类中添加特定的方法;我试着把它尽量缩短以限制重复。在每个类特定消息中,添加以下内容:
public static void addListener (SocketListener s, MessageListener<SpecificMessage> listener)
{
s.addSubclassListener (SpecificMessage.class, listener);
}
addBasicMessageListener
将是为类注册侦听器的方法。因为它的参数不是泛型的,所以您应该能够将它添加到列表中。(注意:对(T)
的类型转换将给您未经检查的警告。)其思想是,如果其他类想要注册一个侦听器来侦听类SpecificMessageXYZ
的消息,它们将使用
SpecificMessageXYZ.addListener (theSocketListener, new MessageListener<SpecificMessageXYZ> () {
...
... a listener that takes a SpecificMessageXYZ parameter
...
});
SpecificMessageXYZ.addListener(socketlistener,newmessagelistener(){
...
…接受SpecificMessageXYZ参数的侦听器
...
});
或者类似的东西。这仍然会涉及向下广播,但它只在一个位置(在addSubclass listener
中),而不必在其他类可能要注册的每个侦听器中执行实例和向下广播。我不知道这是否足以弥补在每个消息类中都必须使用复制方法的不足。但是,如果不使用反射,我想不出其他方法。我认为您要寻找的是Java中不存在的某种有趣的协方差
再一次,我要警告你,我实际上没有尝试过这个,只是我通过编译器运行了它,以确保我没有做任何非法的事情。另外请注意,我对方法/类名的选择往往很糟糕
编辑:我突然想到,虽然(T)message
无法执行检查,但我们已经有了执行检查所需的信息,因此我们应该这样做。与其存储列表,不如存储一个HashMap来提供发布/订阅消息组件,旨在简化经典观察者模式的实现。不必显式地注册专用的侦听器或事件发出对象的实例,而是注册一个类,该类对总线上的某些事件感兴趣:
class MyEventListeningClass {
@Subscribe public void onEvent(MyEvent e) {
// react to event
}
}
...
eventBus.register(new MyEventListeningClass());
通过反射将事件注册并发送给感兴趣的订阅者
正如在问题和其他答案中已经看到的那样,Java的类型系统使得监听器接口很难实现类似的灵活性。对于所讨论的用例,事件总线似乎非常适合,特别是当将来需要添加更多的消息类型时。遗憾的是,这种松散耦合的常见警告适用于:很难推理消息到底会发生什么。不是替代设计,但似乎非常适合您的问题。@Pyranja我喜欢EventBus的外观。我试试看
public <T> void addListener(Class<T>, MessageListener<T>) {...}
private class SampleDataListener implements MessageListener<SampleData> {
...
public void messageReceived(SampleData message) {...}
}
private class OtherDataListener implements MessageListener<OtherData> {
...
public void messageReceived(OtherData message) {...}
}
class MyEventListeningClass {
@Subscribe public void onEvent(MyEvent e) {
// react to event
}
}
...
eventBus.register(new MyEventListeningClass());