我该如何在Java中实现Message类的子类型?

3
我相信这是一个基础的面向对象编程问题 -- 我正在设计一个消息传递系统,其中有几种完全不同的消息格式,但我希望它们都能放置在PriorityBlockingQueue上。我的第一个想法是定义一个抽象类Message,然后为每个消息类型定义扩展Message的子类。但这意味着,在接收端,消息处理器需要识别子类以了解如何处理消息的内容。我现在知道的唯一方法是使用.instanceof()或Class。它似乎不合适。
正如Scott Meyers所写,
“每当你发现自己编写的代码形式是"如果对象是类型T1,则执行某些操作,但如果它是类型T2,则执行其他操作",那么就应该打自己。”
(他接着指出,在多态性中,您应该拥有相同的方法名称,对于每个子类,都有不同的实现。我不知道如何让这个思路在我的情况下起作用--消息类型本身完全不相关。)
出于讨论的目的,这里是我的消息类型:
- ConsoleMessage,标识ConsoleObject和ObjectState。 - CardReaderRequestMessage,不包含任何东西,只是请求“下一张卡片” - CardReaderMessage,包含byte[80]卡片图像和Last Card指示符 - CardPunchMessage,包含byte[80]卡片图像 - CardPunchResponseMessage,不包含任何东西,表示卡片图像已复制到打孔缓冲区中
我认为我必须知道我正在处理的消息类型,因此我认为我不应该使用多态的Messages。我应该如何正确地设计这个?
===== 编辑以提出后续问题 =====
我试图找到一种在某些时候不必识别其子类就能使用多态的Message的方法。建议的方法是在每个子类中覆盖一个process()方法。这是我的(简化的)抽象Message和两个子类:
public abstract class Message {

    public abstract void process() {
        // subclasses of Message implement this
    }

    public static class ConsoleMessage extends Message {
        private int obj;
        private int state;
        public ConsoleMessage(int x, int y) {
            obj = x;
            state = y;
        }
        @Override
        public void process() {
            // do something with obj and state?
        }

    public static class CardReaderMessage extends Message {
        private byte[] card;
        private boolean lastCardIndicator;
        public CardReaderMessage(byte[] c, boolean lc) {
            card = c;
            lastCardIndicator = lc;
        }
        @Override
        public void process() {
            // do something with card and lastCardIndicator
        }
}

每个线程都有一个用于所有“入站”消息的队列。假设我的线程需要等待来自控制台的消息以“恢复”,但同时应接收和处理其他类型的消息:

waitForResumeMessage() {
    while (true) { // the following will block until a msg arrives
        Message msg = inboundMessageQueue.receiveMessage();
        msg.process();    

但是现在呢?process()的某些实现已经将数据移动到了某个地方,但最终我需要能够编写:

        if // msg was ConsoleMessage "resume" command
            return;  // .. from waitForResumeMessage()
    } // else iterate until another message
}

这基本上意味着找出'class msg'属于哪个类。

我做得对吗?我意识到在“事件驱动”模型中,“等待”并不是很合适,但这是一个长时间运行的后台工作者。也许使用process()函数的想法更有用,用来改变指导事件驱动线程的FSM状态?

4个回答

2
你不是疯了。这个论点:
任何时候,当你发现自己在写形如“如果对象是类型T1,则做某事,但如果它是类型T2,则做其他事情”的代码时,就应该打自己。
只是故事的一面。另一面的故事是,保持关注点分离同样重要(甚至更重要)。一个常见的例子是,你不应该将表现层代码添加到模型中,只是因为那是可以通过多态处理的地方。不幸的是,Java语言在同时实现这两个理想方面并没有提供太多帮助。
有许多方法来保持“平行但解耦”的代码。Visitor pattern是其中之一,大开关或if-instance-of块也是其中之一。另一个选择是具体类到具体类的Map。

1

你的想法很好,你应该从一个叫做Message的抽象类或接口开始,这个接口应该是这样的:

     public interface Message {
     void process();
     //some other methods
}

public class MessageType1  implements Message {
    @Override
    public void process() {
      //My special way to process this message
}
}

这样,接收端只需要接收实现您的消息接口的东西,也就是“一条消息”,它并不关心特定类型的消息,它只关心它是否响应处理,因此您可以在那里实现每个特定消息应如何处理。


但是,为了使这个设计良好,它假定 process() 是消息的唯一职责,而 OP 似乎在说这不是这种情况。 - Mark Peters
@Mark Peters 这是正确的,但他可以在接口上声明尽可能多的方法。 - Oscar Gomez
真的,但很可能无法遵循单一责任原则或关注点分离,我认为这比避免“switch”设计问题更重要。 - Mark Peters
我必须承认,我还没有从Animal.talk()到Message.process()这个思维跳跃。实际上,我认为它只需要能够做到这一点就可以了。我感谢大家对模板方法、参数多态性甚至注释的反馈 - 对我来说,这些都是相对高级的主题,但我很快就期待掌握设计模式的诀窍。 - Chap

1
你需要的是模板方法。这意味着你在 Message 中定义了一些方法,接收方可以使用相同的接口调用这些方法,但根据实际对象的类型,任何一种 Message 类型中被覆盖的方法都会被调用。

这在Java中是什么样子? - Mark Peters
基本上就是OscarMk在上面的回答。http://en.wikipedia.org/wiki/Template_method_pattern - Murali VP
啊,我被你的话“你想要的不是多态”弄糊涂了。奥斯卡的例子绝对是多态的一个例子,因为接收端只会处理“消息”。 - Mark Peters
已解决。我想的是http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming#Parametric_Polymorphism。 - Murali VP
@Murali:我无法理解你的答案,特别是以“那是什么意思…”开头的句子。你能否尝试将它改写成几个较短的句子? - Chap

1

除了已经提出的解决方案,我想提一下,您可以在不使用任何接口或抽象类的情况下完成它。

一个人可以创建自己的注释,其中包含消息对象可以被处理的信息。

在实现时: 您可以相应地注释您的类。

在运行时: 当您从队列中弹出下一个元素时,您可以检查注释并调用能够处理当前实例的实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接