Akka/Java:在自定义参与者中处理多种消息类型?

Akka/Java:在自定义参与者中处理多种消息类型?,java,akka,messaging,Java,Akka,Messaging,要在Akka(Java绑定)中实现您自己的自定义参与者,您需要扩展基类。这要求您定义自己的onReceive(…)方法: @Override public void onReceive(Object message) { // TODO } 目前的问题是确定一种消息处理策略,使参与者能够处理多种类型的消息。一种策略是使用反射/类型。这里的问题是: 它迫使我们创建空的“shell类”,这些类只提供消息的语义含义(见下文);及 它占用消息参数,阻止我们传递任何动态或有意义的内容 空壳类的示

要在Akka(Java绑定)中实现您自己的自定义参与者,您需要扩展基类。这要求您定义自己的
onReceive(…)
方法:

@Override
public void onReceive(Object message) {
    // TODO
}
目前的问题是确定一种消息处理策略,使参与者能够处理多种类型的消息。一种策略是使用反射/类型。这里的问题是:

  • 它迫使我们创建空的“shell类”,这些类只提供消息的语义含义(见下文);及
  • 它占用
    消息
    参数,阻止我们传递任何动态或有意义的内容
  • 空壳类的示例:

    public class EmptyShellMessage { }
    
    然后在
    onReceive
    方法中:

    @Override
    public void onReceive(Class<?> message) {
        if(message.isAssignableFrom(EmptyShellMessage.class)) {
            // TODO
        } else {
            // TODO
        }
    }
    
    但是这里我们使用的是
    instanceof
    ,这被许多人认为是一个巨大的反模式(只是谷歌的“instanceof antipattern”)

    然后我们有枚举:

    public enum ActorMessage {
        FizzEvent,
        BuzzEvent,
        FooEvent,
        BarEvent
    }
    
    现在,
    onReceive
    看起来像:

    @Override
    public void onReceive(ActorMessage message) {
        if(message.equals(ActorMessage.FizzEvent)) {
            // TODO
        } else {
            // TODO
        }
    }
    
    这里的问题是,我们可能有一个大型actor系统,需要处理数百甚至数千种不同的事件/消息类型。此枚举变大且难以维护。它还有与上面的反射策略相同的问题,即它阻止我们在参与者之间发送任何动态信息

    我能想到的最后一件事是使用字符串:

    @Override
    public void onReceive(String message) {
        if(message.equals("FizzEvent")) {
            // TODO
        } else {
            // TODO
        }
    }
    
    但我讨厌这个。时期句末


    所以我问:我是否遗漏了一些显而易见的东西,或者是另一种策略?Java/Akka应用程序应该如何处理大量的事件/消息类型,并在
    onReceive
    方法中指定它们正在处理的事件/消息类型?

    不,您不会遗漏任何内容。我也不是球迷。在Scala中,它要好一点,因为onReceive方法可以被调出以模拟协议的变化状态,并且它使用的部分函数比if/elseif/else稍微好一点。。。但它仍然令人讨厌。它在Erlang中更好,这是该模型的发源地,但也就是说,考虑到akka团队所面临的局限性,他们做出了正确的设计选择,并做了出色的工作

    另一种策略是执行双重调度。因此,将命令传递给要执行操作的参与者,或者在映射中查找消息的处理程序。阿克卡特工基本上是前者,当习惯了他们的实力时,他们是相当不错的。但一般来说,双重分派只会增加复杂性,因此在大多数情况下,只需习惯标准方法即可。这在java中表示if instanceof或switch语句


    为了完整起见,这里提供了一个双重分派(psuedo代码)示例,以帮助理解。作为一种方法,它附带健康警告,所以我重申;标准方法是标准的,有一个原因,人们应该在99%的时间里使用它

    onReceive( msg ) {
        msg.doWork()
    }
    
    这种方法的问题是,现在消息需要知道如何处理自己,这是肮脏的;它会导致紧密耦合,并且可能很脆弱。恶心

    因此,我们可以使用查找处理程序(命令模式样式)

    这里的问题是,现在还不清楚这些命令是什么,遵循代码需要更多的跳跃。但有时这是值得的


    正如罗兰所指出的,在传递对演员本身的引用时必须小心。上面的例子并没有与这个问题相冲突,但这很容易引起注意。

    我强烈建议不要绕过参与者的
    这个
    引用,因为这很容易让您跨越执行上下文边界来传递它,这可能会以完全不明显的方式发生。另一个问题是,它需要消息类型知道参与者想要如何处理它,这与消息传递应该如何解耦不同实体正好相反:参与者必须选择如何处理消息,而不是相反


    参与者是动态实体,他们可以以不可预见的顺序接收输入(这是模型的核心),因此我们必须使用动态语言特性来实现它们
    instanceof
    是该语言的一部分,用于促进消息类型的运行时发现。不管它是否在其他场合被滥用,也不管谁称它为反模式,它都是这项工作的正确工具。其他语言(包括古老的Erlang)中的Actor实现使用模式匹配来实现同样的效果,模式匹配与
    instanceof
    测试完全相同(加上更方便的参数提取).

    我为Java 8创建了一个简单的模式匹配DSL,它对Akka参与者非常有用:


    我同意框架会促使你做一些根本不优雅的事情,而且我也不会写这样的代码。 所以我的方法是这样的。 1) 接口:

    public interface IAnswerable {
        Object getAnswer();
    }
    
    2) 演员:

    @Override
    public void onReceive(Object message) throws Throwable {
        IAnswerable iAnswerable = (IAnswerable) message;
        getSender().tell(iAnswerable.getAnswer(), getSelf());
    }
    
    3) 我用作消息的类:

    public class MyClass implements IAnswerable {
    
    @Override
    public Object getAnswer() {
        // Whatever you need to do here
        return null;
    }
    
    }

    这是一个原始的方法,但你得到的想法。它优雅易读,防止演员做大


    希望有帮助;)

    啊,谢谢@Chris K(+1)-很高兴知道我不是唯一一个觉得这很尴尬的人。我并不完全熟悉双重分派,但从我的谷歌搜索中,我似乎只会为我的演员需要处理的每种消息类型编写一个
    onReceive
    重载。因此,如果我想让我的演员处理
    Fizz
    Buzz
    消息,我会有两种
    onReceive
    方法:(1)
    public void onReceive(Fizz-Fizz)
    和(2)
    public void onReceive(Buzz-Buzz)
    。这是正确的吗?再次感谢@smeeb在消息上还需要一个接受参与者的抽象方法。在每个具体的消息实现中,此方法将消息传递给参与者。我本打算提出这个建议,但我不喜欢它对
    public interface IAnswerable {
        Object getAnswer();
    }
    
    @Override
    public void onReceive(Object message) throws Throwable {
        IAnswerable iAnswerable = (IAnswerable) message;
        getSender().tell(iAnswerable.getAnswer(), getSelf());
    }
    
    public class MyClass implements IAnswerable {
    
    @Override
    public Object getAnswer() {
        // Whatever you need to do here
        return null;
    }