Akka/Java:处理自定义actor中的多个消息类型?

sme*_*eeb 6 java messaging akka

要在Akka(Java绑定)中实现自己的自定义actor,可以扩展UntypedActor基类.这需要您定义自己的onReceive(...)方法:

@Override
public void onReceive(Object message) {
    // TODO
}
Run Code Online (Sandbox Code Playgroud)

手头的问题是确定一种消息处理策略,使actor能够处理多种类型的消息.一种策略是使用反射/类型.这里的问题是:

  1. 它迫使我们创建空的"shell类",除了为消息赋予语义之外什么都不做(见下文); 和
  2. 它占用message参数并阻止我们传递任何动态或有意义的东西

空shell类的示例:

public class EmptyShellMessage { }
Run Code Online (Sandbox Code Playgroud)

然后在该onReceive方法中看起来像:

@Override
public void onReceive(Class<?> message) {
    if(message.isAssignableFrom(EmptyShellMessage.class)) {
        // TODO
    } else {
        // TODO
    }
}
Run Code Online (Sandbox Code Playgroud)

因此,我们不仅创建了一个无用的类,而且由于Object message现在用于确定消息的类/类型,我们不能使用它来包含任何更多的信息,尤其是另一个actor可能的动态/运行时信息想传递它.

有时我会看到这种变化:

@Override
public void onReceive(Object message) {
    if(message instanceof FizzEvent) {
        // TODO
    } else {
        // TODO
    }
}
Run Code Online (Sandbox Code Playgroud)

但在这里我们使用的instanceof许多人认为是一个巨大的反模式(只是谷歌" 反对模式的实例 ").

然后我们有枚举:

public enum ActorMessage {
    FizzEvent,
    BuzzEvent,
    FooEvent,
    BarEvent
}
Run Code Online (Sandbox Code Playgroud)

现在onReceive看起来像:

@Override
public void onReceive(ActorMessage message) {
    if(message.equals(ActorMessage.FizzEvent)) {
        // TODO
    } else {
        // TODO
    }
}
Run Code Online (Sandbox Code Playgroud)

这里的问题是我们可能有一个大型的actor系统,需要处理数百甚至数千种不同的事件/消息类型.这个枚举变得很大并且难以维护.它也有与上面的反射策略相同的问题,它阻止我们在actor之间发送任何动态信息.

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

@Override
public void onReceive(String message) {
    if(message.equals("FizzEvent")) {
        // TODO
    } else {
        // TODO
    }
}
Run Code Online (Sandbox Code Playgroud)

但我讨厌这个.期.句末.

所以我问:我在这里错过了一些明显的东西,也许是另一种策略?Java/Akka应用程序应该如何处理大量的事件/消息类型,并指定它们在onReceive方法中处理哪一个?

Chr*_*s K 6

不,你没有遗漏任何东西.我也不是粉丝.在Scala中它更好一点,因为onReceive方法可以被换出来模拟协议的变化状态,它使用的部分函数比if/elseif/else好一点......但它仍然很蹩​​脚.它在Erlang中更好,这是该模型的起源但是,鉴于akka团队面临的局限性,他们做出了正确的设计选择并做得非常出色.

另一种策略是执行双重调度.因此,将命令传递给actor作为动作,或者在map中查找消息的处理程序.Akka特工基本上是前者,当他们的力量使用时非常好.但总的来说,双重调度只会增加复杂性,因此对于大多数情况,人们只需要习惯标准方法.在java中,这意味着是instanceof还是switch语句.


双重调度(伪代码)的一个示例,此处包含完整性,以帮助理解.作为一种方法,它带有健康警告,所以我重申; 标准方法是标准的原因,并且应该使用99%的时间.

onReceive( msg ) {
    msg.doWork()
}
Run Code Online (Sandbox Code Playgroud)

这种方法的问题在于,现在消息需要知道如何处理自己,这很脏; 它会引起紧密耦合并且可能很脆弱.呸.

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

val handlers = Map( "msgid1"->Handler1, "msgid2->Handler2 )

onReceive( msg ) {
    val h = handlers(msg.id)

    h.doWork( msg )
}
Run Code Online (Sandbox Code Playgroud)

这里的问题是,现在还不清楚命令是什么,并且遵循代码通过涉及跳转更多.但有时候这是值得的.

正如Roland所指出的那样,在传递对演员本身的引用时必须小心.上面的例子并没有违背这个问题,但这很容易诱惑.

  • @smeeb你还需要一个接受你的演员的消息的抽象方法.在每个具体的消息实现中,此方法将消息传递给actor.我打算建议这一点,但我不喜欢这个从消息到actor的编译时依赖.你不想让你的消息不知道演员吗?我想这可以通过为你的actor声明一个协议接口来实现(所有重载的onReceive方法),然后你的消息可能依赖于此. (2认同)