3

我在设计一个干净的解决方案以使用观察者模式样式解决方案发送不同类型的消息时遇到问题。

我有一个客户端应用程序通过 tcp 套接字连接到服务器(我无法更改)。我可以发送和接收 json 编码的消息,这些消息将始终包含一个“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 将所有消息解析到只有“msg”参数的“BasicMessage”类中。我有一个 Map 将所有消息类型字符串映射到它们各自的类。一旦我有了“msg”参数,我就可以查找我需要用 Gson 反序列化它的类,然后这样做。现在我有一个正确类的实例,但这就是我的设计开始崩溃的地方。

我希望其他各种类能够仅订阅几种类型的消息。问题是我似乎无法找到一种方法来做到这一点,而不需要一堆 instanceof 或在每个客户端中重新解析所有内容。

我最初的想法是使用像这样的参数化接口:

public interface MessageListener<T> {
    public void onReceivedMessage(T message);
}

然后在反序列化消息的类中,我有一个List<MessageListener<Message>>Message 是每个其他 Message 继承自的抽象类。然后我遇到了MessageListener<SpecificMessage>不继承的类型擦除问题,MessageListener<Message>所以我没有办法将客户端添加到一个简单的列表中。似乎我必须为每种类型的消息都有一个列表,这也不理想。这种设计的另一个问题是它会限制我只对一个需要多个消息的类中的各种消息侦听器使用匿名内部类,因为即使你用不同的类参数化它,你也不能两次实现相同的接口.

有没有更好的模式可以用于这种情况?我觉得我可能不得不使用反射来让它“整齐”地工作。理想情况下,如果我想添加另一种消息类型,我希望它只需要添加要反序列化的类,也许还需要从其 msg 字符串到该类的映射,然后能够开始为该类型的消息添加侦听器另一个班级。

提前致谢!

4

3 回答 3

1

而不是一个列表,如何存储一个HashMap<Class<? extends Message>, List<MessageListener<? extends Message>>?当消息进入并且您有不确定数量的听众对不确定数量的事件感兴趣时,这可以为您提供更简单的查找。只需查找对您的反序列化消息类型感兴趣的侦听器列表即可。您已对其进行设置,以便您只能以一种为您提供编译时检查的方式添加到地图中:

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) {...}
}
于 2013-10-10T00:51:19.023 回答
1

guava 事件总线提供了一个发布/订阅消息传递组件,旨在简化经典观察者模式的实现。不必显式地向事件发射对象注册专用ListenerObserver实例,而是注册一个类,该类对总线上的某些事件感兴趣:

class MyEventListeningClass {
   @Subscribe public void onEvent(MyEvent e) {
      // react to event
   }
}
...
eventBus.register(new MyEventListeningClass());

向感兴趣的订阅者注册和分发事件是通过反射完成的。

正如在问题和其他答案中已经看到的那样,Java 的类型系统很难通过侦听器接口实现类似的灵活性。对于有问题的用例,事件总线似乎很合适,尤其是当将来需要添加更多消息类型时。唉,这种松散耦合的常见警告适用:很难推理,消息究竟会发生什么。

于 2013-10-10T08:21:39.277 回答
0

这是一种可能性(但请注意,我尚未对其进行测试)。不幸的是,它需要将某种方法添加到 ; 的每个子类中BasicMessage。我试图让它尽可能短以限制重复。在每个类SpecificMessage中,添加以下内容:

public static void addListener (SocketListener s, MessageListener<SpecificMessage>               listener)
{
    s.addSubclassListener (SpecificMessage.class, listener);
}

我假设SocketListener是监听套接字并稍后向订阅者发送消息的类。(您可以将其更改为合适的。)

SocketListener

private interface BasicMessageListener {
    public void onReceivedMessage (BasicMessage message);
}

protected <T extends BasicMessage> void addSubclassListener (final Class<?> clazz, final MessageListener<T> listener)
{
    addBasicMessageListener (clazz, new BasicMessageListener () {
        public void onReceivedMessage (BasicMessage message) {
            if (message.getClass() != clazz)
                throw new ClassCastException ();
            listener.onReceivedMessage ((T) message);
        }
    }
}

addBasicMessageListener将是为该类注册侦听器的方法。由于它的参数不是通用的,您应该能够将其添加到列表中。(注意:类型转换(T)会给你未经检查的警告。)这个想法是,如果其他类想要注册一个监听器来监听 class 的消息SpecificMessageXYZ,他们会使用

SpecificMessageXYZ.addListener (theSocketListener, new MessageListener<SpecificMessageXYZ> () {
    ...
    ... a listener that takes a SpecificMessageXYZ parameter
    ...
});

或类似的东西。这仍然会涉及向下转换,但它只会在一个地方(in addSubclassListener),而不是必须instanceof在其他类可能想要注册的每个侦听器中进行一次向下转换。我不知道这是否足以弥补必须将重复的方法放在每个消息类中的收益。但是,如果不使用反射,我想不出另一种方法。我认为您正在寻找的是 Java 中不存在的某种有趣的协方差。

再一次,我会警告你,我实际上并没有尝试过这个,除了我通过编译器运行它以确保我没有做任何非法的事情。另请注意,我对方法/类名的选择往往很糟糕。

编辑:我想到虽然(T) message无法进行检查,但我们已经有了进行检查所需的信息,所以我们应该这样做。

于 2013-10-10T00:47:52.227 回答