3

我现在正在玩 Scala,并试图找出一些关于如何设计类的最佳实践。(一周左右开始尝试 Scala。)

从我的 Erlang 时代开始,我就非常喜欢消息传递和基于 actor 的软件。在大多数 Scala 示例中,actor 类是这样实现的:

object Foo
object Bar
class MyActor extends Actor {
  def receive = {
    case Foo => ...
    case Bar => ...
    case _ => ...
  }
}

但是我从我的面向对象(接口和多态)载体中学到的东西告诉我,这个概念不是很灵活。

MyActor 可以被 MyAdvancedActor 替换,但没有定义 MyActor 实现需要实现哪些消息的合同。

当我考虑在 Scala 中编写 Actor 时,我倾向于编写一个 trait 来指定一些方法。MyActor 实现需要实现这个方法,在这些方法中,它可以向自己发送自己的私人消息。使用这种方法,我们有一个指定的接口,并且可以以类型安全的方式替换 MyActor 实现。

在我阅读 scala 教程和示例的时候,我没有遇到过这样的类设计。这不是常识还是在Scala中有更好的方法?还是这些教程太小而无法涵盖这样的主题?

4

2 回答 2

5

通常的做法是在这种情况下使用代数数据类型:您可以sealed为所有消息创建一个基本类型,如下所示:

sealed trait MyActorMessages
object Foo extends MyActorMessages
object Bar extends MyActorMessages

但是这种契约不是由编译器强制执行的。您可以使用Typed Channels来执行此合同:

class MyActor extends Actor with Channels[TNil, (MyActorMessages, MyActorReply) :+: TNil] {
  channel[MyActorMessages] { (req, snd) ⇒
    req match {
      case Foo  ⇒ ...
      case Bar ⇒ ... // You'll get a warning if you forget about `Bar`
    }
  }
}

编译器将强制您(带有警告)处理所有可能的消息类型(在这种情况下为 的所有子类型MyActorMessages),并且发送者将被迫使用<-!-方法仅发送有效消息(带有编译错误)。

请注意,发件人也可以使用不安全的方法!发送无效消息。

于 2013-08-08T07:12:10.787 回答
2

我真的很喜欢@senia 的解决方案。这是对 Akka 新的 Typed Channels 功能的有效使用。但是,如果该解决方案不适合您,我可以为 OO 世界提供一些更传统的东西。在此解决方案中,您可以通过构造 Actor 的策略实现来指定 Actor 的实际消息处理行为。代码看起来像这样:

//Strategy definition
trait FooStrategy{
  def foo1(s:String):String
  def foo2(i:Int):Int
}

//Regular impl
class RegularFoo extends FooStrategy{
  def foo1(s:String) = ...
  def foo2(i:Int) = ...
}

//Other impl
class AdvancedFoo extends FooStrategy{
  def foo1(s:String) = ...
  def foo2(i:Int) = ...
}

//Message classes for the actor
case class Foo1(s:String)
case class Foo2(i:Int)

//Actor class taking the strategy in the constructor
class FooActor(strategy:FooStrategy) extends Actor{      
  def receive = {
    case Foo1(s) => sender ! strategy.foo1(s)        
    case Foo2(i) => sender ! strategy.foo2(i)
  }
}

然后创建这个actor的实例:

val regFooRef = system.actorOf(Props(classOf[FooActor], new RegularFoo))
val advFooRef = system.actorOf(Props(classOf[FooActor], new AdvancedFoo))

这里的一个好处是您将参与者的业务逻辑与其正常的消息处理行为分离。您让演员类只做演员的事情(从邮箱接收,回复发件人等),然后将真正的业务逻辑封装在一个特征中。这也使得使用 trait impls 的单元测试单独测试业务逻辑变得更加容易。如果您在 trait impls 中需要演员类型的东西,那么您总是可以ActorContext为 on 方法指定一个隐式的FooStrategy,但是您将失去演员和业务逻辑的完全解耦。

就像我之前说的,我喜欢@senia 的解决方案;我只是想给你另一个可能更传统的面向对象的选择。

于 2013-08-08T12:30:41.013 回答