我正在玩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中执行此操作?或者这些教程只是为了涵盖这样一个主题?
答案 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选项。