如何正确等待多条消息并在此之后成为新状态

时间:2017-08-09 10:40:42

标签: scala akka

我有一个演员,应该收到两条消息,然后成为新的初始化状态。我写了一些代码,但看起来很难看:

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检查和重复代码。我如何简化我的实施并使其正确?

4 个答案:

答案 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

主要想法是使用expectexpectOnce来接收某些消息。一旦发生这种情况,你可以做任何其他事情。