在面向对象Java中使用反模式

4
阅读了一篇有趣的文章后,我对此有几个问题。请参考[zero-turn-around]上的常见陷阱#8:假装Java更像C(即不理解OOP)
我同意作者对于这个陷阱的解决方案。我的代码也面临着类似的问题(滥用instanceof)。但是,我无法按照作者建议的方式实现代码。我的场景如下:
1. 我使用JMS作为消息总线。 2. 消息在系统中流动。侦听器通常侦听消息。 3. 所有消息都有一个单一的IMessage父对象。 4. 我使用instanceof来区分消息。 5. 侦听器通常执行特定于领域的业务逻辑。
如果我同意作者的解决方案,我将不得不在Message类中实现特定于领域的业务逻辑,这会使我的轻量级消息对象臃肿。不仅如此,我的消息对象现在将具有业务(领域)行为,这我认为是不公平的,因为它们只是消息对象。
那么,这个问题的合理解决方案是什么呢?下面是示例代码。
public void onMessage(IMessage mssg)
{

    if(mssg instanceof MoPn){
       ...
    } else if(mssg instance of MoZn){
       ...
    } else if(mssg instance of MoLn){
       ...
    }
}

4
在 Message 类中使用特定于域的业务逻辑是有问题的吗?面向对象建议一种特定的方式来拆分类。但与此同时,您希望保持消息对象“轻量级”且无逻辑。这些目标相互冲突。个人而言,我不会担心将业务逻辑放入消息中。毕竟,消息意味着特定的事情,将对应于该含义的逻辑放入其中有何不妥之处?在类中添加逻辑并不会使消息(实例)传输更加困难。否则,测试实例的类是可以的。 - AgilePro
@AgilePro 嗯,问题在于“侦听器”列表在不断增长。这些侦听器确实执行非常专业化的业务逻辑。例如,在接收到ALARM消息时,应该触发SNMP陷阱。此外,在接收到警报时,必须计算客户特定的表示形式。如果我开始在Alarm消息中完成所有这些操作,那么我担心Alarm对象很快就会难以维护。这不是一个反模式吗? - TheMonkWhoSoldHisCode
2
是的。有几种情况下,您希望忽略按类选择逻辑的建议。如果将相同的消息发送到具有不同行为的不同客户端,则可能不希望将逻辑放在消息类中。如果消息类是标准化(共享)的,并且您没有权利更改它们,也是如此。在这些情况下,仅测试实例的类即可。您可以创建一个包装类来执行此操作,但原则上并没有什么不同。面向对象编程风格的告诫不适用于此情况。 - AgilePro
你可以看一下Scala/Akka是如何处理这个问题的。在这种情况下,使用更加功能化的样式和模式匹配比面向对象更容易、更灵活。这些函数式模式也适用于Java,但不像Scala那样完美。 - Albert
1
我是与zeroturnaround文章相关联的作者。我的观点在第8点上是,一些开发人员不理解面向对象编程和过程式编程之间的区别。这就是反模式(不理解面向对象编程)。如果你两者都理解,并且认为在你的情况下过程式编程更好,那么就使用它。你不必在任何地方都应用面向对象编程。 - kazanaki
3个回答

3

一个合理的解决方案是创建知道如何处理特定类型消息的监听器。

这是接口。

interface MessageListener<M extends IMessage> {
    void onMessage(M message);
}

这是其中一个类的框架:

这里是其中一个类的框架:

class MoZnListener implements MessageListener<MoZn> {
    public void onMessage(MoZn message) {
    }
}

现在,您可以创建消息和监听器之间的映射关系。您可以使用属性文件、硬编码映射或注解。这取决于您。一旦拥有了它,您就可以实现一个类似下面的JMS消息监听器:
class MessageEntryPoint implements MessageListener {
    public void onMessage(Message message) {
        getMessageHandler(message).onMessage(message.getObject());
    }
}

现在您的系统是可扩展的,不需要 instanceof。要引入新的消息类型,您只需要创建一个实现 IMessage 接口的适当类和支持它的监听器。
顺便说一下,更多注释:
1. 使用 I 作为标记接口并不符合 Java 风格。 2. 我不熟悉您的领域,可能像 MoPn 这样的名称对您来说是自解释的,但在我看来并不是。尝试使用更具自解释性的标识符。

2
你能够完善一下 getMessageHandler 的例程吗?我认为除非有些我不知道的神奇操作,否则仍需要调用 instanceof - AgilePro
我支持你的想法 @AgilePro。我认为如果没有instanceof,就不可能评估消息类型。 - TheMonkWhoSoldHisCode
1
getMessageHandler 根据创建的映射返回特定消息的消息处理程序。它可能使用实例,但仅限一个,在迭代映射的循环内部使用。 - dieend

3

这可能会让一些纯粹主义者感到不满,但我认为使用具有面向对象特性的语言并不意味着您必须将面向对象应用于所有内容。

消息只是模式。它们携带数据。强制在仅仅从一个地方传递数据到另一个地方的结构上运用面向对象可能也是一种反模式。


0

将逻辑与数据模型分离有时被认为是违反面向对象编程的原则,但有时为了解耦合是必要的。发送消息的代码不需要知道如何响应消息。

instanceof 的一些其他问题是,在添加新类型时很难找到需要更新的内容,并且没有办法确保所有情况都被覆盖。这些问题可以通过添加一些新接口来解决:

interface Message {
    void callHandler(MessageHandler handler);
}

interface MessageHandler {
    default void onMoPn(MoPn message) {
    }

    default void onMoZn(MoZn message) {
    }
}

class MoPn implements Message {
    public void callHandler(MessageHandler handler) {
        handler.onMoPn(this);
    }
}

class Mozn implements Message {
    public void callHandler(MessageHandler handler) {
        handler.onMoZn(this);
    }
}

并添加一个处理程序:

class MessageHandlerImpl implements MessageHandler {
    public void onMoPn(MoPn message) {
        System.out.println("MoPn message: " + message);
    }
}

当您收到一条消息时,可以调用处理程序,如下所示:

MessageHandler handler = new MessageHandlerImpl()
Message message = new MoPn();

message.callHandler(handler);

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