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