基于Actor的类,有或没有接口

时间:2013-08-08 06:39:20

标签: scala akka class-design actor

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

由于我的Erlang时间,我是消息传递和基于演员的软件的忠实粉丝。在大多数Scala示例中,actor类的实现方式如下:

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

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

MyActor可以被MyAdvancedActor取代,但是没有合约定义了MyActor实现需要实现哪些消息。

当我考虑在Scala中编写Actors时,我倾向于编写一个指定某些方法的特征。 MyActor实现需要实现此方法,其中可以将自己的私有消息发送给自己。 使用这种方法,我们有一个指定的接口,可以以类型安全的方式替换MyActor实现。

在我阅读scala教程和示例的时候,我没有遇到过这样的课程设计。这不是常识,还是有更好的方法在Scala中执行此操作?或者这些教程只是为了涵盖这样一个主题?

2 个答案:

答案 0 :(得分: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的所有子类型),并且发送者将被强制使用<-!-方法仅发送有效消息(带有编译错误)。

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

答案 1 :(得分: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))

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

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