我有一个演员,应该收到两条消息,然后成为新的初始化状态。我写了一些代码,但看起来很难看:
def waitInitialisation(@Nullable one: Integer, @Nullable two: String): Receive = {
case _one: Int =>
if (two == null)
context.become(waitInitialisation(_one, two))
else {
doSomething()
context.become(initialised(_one, two))
}
case _two: String =>
if (one == null)
context.become(waitInitialisation(one, _two))
else {
doSomething()
context.become(initialised(one, _two))
}
}
def initialised(one: Int, two: String): Receive = ???
override def receive: Receive = waitInitialisation(null, null)
所以问题,我看到的:null检查和重复代码。我如何简化我的实施并使其正确?
答案 0 :(得分:1)
@chunjef已经为使用选项提供了一个很好的方向,这是Scala的方法。我正在让其他两个选项使用模式匹配来使代码更漂亮。
在您看一下这两个解决方案之前,请记住,通常不建议在.get
上调用Option
,并且可能会收到一些编译器警告。无论如何,我们总是确保在我们的示例中做出正确的调用,因为我们事先要检查选项isDefined
。
哦,当处理可能来null
的值时 - 就像使用Java API一样 - 总是使用Option的apply,而不是Some的适用。
第一个定义了基本相同的方法,但结构略有不同:
def waitInit(one: Option[Int], two: Option[String]): Receive = {
case value: Int if two.isDefined =>
context.become(initialised(value, two.get))
case value: Int =>
context.become(waitInit(Option(value), two))
case value: String if one.isDefined =>
context.become(initialised(one.get, value))
case value: String =>
context.become(waitInit(one, Option(value)))
}
override val receive = waitInit(None, None)
第二个将这个逻辑分成两部分,这样你就可以更容易地遵循它了:
def waitOne(two: Option[String]): Receive = {
case one: Int if two.isDefined =>
context.become(initialised(one, two.get))
case one: Int =>
context.become(waitOne(two) orElse waitTwo(Option(one)))
}
def waitTwo(one: Option[Int]): Receive = {
case two: String if one.isDefined =>
context.become(initialised(one.get, two))
case two: String =>
context.become(waitOne(Option(two)) orElse waitTwo(one))
}
override val receive: Receive =
waitOne(None) orElse waitTwo(None)
就是这样,我没有在这里放一些代码(比如initialised
的定义),因为它是相同的。
享受:)
答案 1 :(得分:0)
使用Option
是Scala处理空值的惯用方法:
def waitInitialisation(one: Option[Int], two: Option[String]): Receive = {
case _one: Int =>
two match {
case Some(s) =>
doSomething()
context.become(initialised(_one, s))
case None =>
context.become(waitInitialisation(Option(_one), None))
}
case _two: String =>
one match {
case Some(i) =>
doSomething()
context.become(initialised(i, _two))
case None =>
context.become(waitInitialisation(None, Option(_two)))
}
}
def initialised(one: Int, two: String): Receive = ???
def receive = waitInitialisation(None, None)
对于“代码重复”,我不会挂断become
次呼叫的数量。你的演员可以处于以下四种状态之一:
waitInitialisation(None, None)
waitInitialisation(Some, None)
waitInitialisation(None, Some)
initialised
您可能可以使用FSM
特征实现状态更改,但这对您的案例来说可能过度。你构建演员的方式很简单明了。
答案 2 :(得分:0)
您还可以使用其他消息执行此操作。 使用该解决方案,添加或更改init的消息将非常简单:
case class InitState(
one : Option[Int],
two : Option[String],
three : Option[Boolean]
)
{
def fire() : Unit = {
context.become(waitInit(this))
self ! this
}
}
def waitInit(st : InitState = InitState(None, None, None)) : Receive = {
case i : Int =>
st.copy(one = Some(i)).fire()
case s : String =>
st.copy( two = Some(s)).fire()
case b : Boolean =>
st.copy(three = Some(b)).fire()
case InitState(Some(i : Int), Some(s : String), Some(b : Boolean)) =>
context.become(afterInit(i, s, b))
case _ : InitState =>
}
def afterInit(one : Int, two : String, three : Boolean) : Receive = ???
def receive = waitInit()
正如chunjef所写,对于不那么简单的案例,好的选择将是使用FSM。
答案 3 :(得分:0)
akka-contrib
提供了与您要查找的类似的聚合器模式:https://github.com/akka/akka/blob/master/akka-contrib/src/main/scala/akka/contrib/pattern/Aggregator.scala
现在不推荐使用此代码,但您可以将其复制到项目中。
在这里你可以找到它的工作原理(这个文档非常陈旧):http://doc.akka.io/docs/akka/2.3.0/contrib/aggregator.html
主要想法是使用expect
或expectOnce
来接收某些消息。一旦发生这种情况,你可以做任何其他事情。