我一直在使用Akka和Scala大约一个月,我对使用消息替换显式接口感到有些困扰。考虑以下简单的Akka Actor:
case class DoMyHomework()
class Parent extends Actor {
def receive = {
case d: DoMyHomework => // do nothing
}
}
演员或非演员代码,向此演员发送DoMyHomework消息,如下所示:
ActorRef parent = ...
parent.ask(DoMyHomework)
不知道结果会是什么。答案的类型是什么?我能得到答案吗?我能得到例外吗?等等。
修复似乎是记录案例类......但是如果其他一些actor也接收同一个案例类怎么办呢。然后文档应该接收该消息应该在actor本身。
为了努力清理这一点,我想到了以下几点:
trait SomeoneSmarter {
def wouldYouDoMyHomework: Future[Boolean]
}
class Parent extends Actor with SomeoneSmarter {
case class DoMyHomework()
def wouldYouDoMyHomework = {
(self ? DoMyHomework()).mapTo(Boolean)
}
def receive = {
case d: DoMyHomework =>
// TODO: If I'm busy schedule a false "No way" reply for a few seconds from now.
// Just to keep their hopes up for a while. Otherwise, say sure right away.
}
}
所以,我与同事聊起这个,其中一个反应是“你不是真的对演员模特。”
首先,我非常感谢那些长时间使用Actors的人的一些指导。所有的消息都变得笨拙吗?你最后隐藏了接口后面的消息传递吗?
我提议的演员仍然可以选择在他们之间发送消息,订阅事件流,以及你对Akka所期望的所有东西。界面为您提供了一种经过时间考验的方式来了解您正在与之交谈的内容。在IDE中进行编码时,它会有所帮助,等等。为什么一个演员的用户需要知道它是一个演员(除非它也是一个演员而且与它紧密结合)?
我得到的另一个反应是“看起来你想要一个TypedActor”。但在阅读了有关TypedActor之后,我并不相信。当然TypedActor可以省去创建这些内部消息的麻烦。但是,至少来自代码示例 http://doc.akka.io/docs/akka/snapshot/scala/typed-actors.html我得到的印象是,TypedActor仅用作围绕要封装的代码块的代理,或者是线程安全的,或者根本不直接从当前线程调用。你编码的只是实现和接口。你不要弄乱演员本身(代理人) - 例如如果您希望您的实现执行定期工作或订阅事件流,或执行与该接口无关的任何其他操作。
我还阅读了http://letitcrash.com/post/19074284309/when-to-use-typedactors,并没有发现这个例子更具启发性。我可能只是不喜欢TypedActor(不是我声称我真的了解Actors)。
事先感谢您的帮助。
Pino的
答案 0 :(得分:11)
首先让我回答我认为非常重要的一点。你说:
为什么一个演员的用户需要知道它是一个演员(除非它也是一个演员而且与它紧密结合)?
演员是一种与传统OO截然不同的编程范式,主要区别在于一切都是异步的,因此永远不会有真正的“返回值”。这意味着隐藏它是演员的事实通常是一个坏主意,因为异常引用my TypedActors blog post。关于actor的最好的事情是它们完全被封装在ActorRef
之后的Akka中 - 而不是OO语言的弱封装。为了充分利用它,尽可能公开ActorRef
,这使客户端代码有机会以最合适的方式使用它们(可能使用tell
或ask
取决于上下文)。
在编写演员时,您应该将此演员的所有内容放在一个位置,包括界面合约说明。看起来有点像这样:
object Parent {
/**
* Send this message to make your parent do your homework … yeah, right ;-)
*/
case object DoHomework
}
/**
* This actor will do your homework if asked to.
*
* ==Actor Contract==
*
* ===Inbound Messages===
* - '''DoHomework''' will ask to do the homework
*
* ===Outbound Messages===
* - '''HomeworkResult''' is sent as reply to the '''DoHomework''' request
*
* ===Failure Modes===
* - '''BusinessTripException''' if the parent was not home
* - '''GrumpyException''' if the parent thinks you should do your own homework
*/
class Parent extends Actor {
…
}
使用普通的无类型actor可以让你充分利用actor模型的全部功能,包括动态改变行为,而不是想把自己装入“同步”调用的超时保护笼子里(简而言之,TypedActors是在幕后使用actor实现传统同步接口时非常有用。我同意IDE支持消息类型会很好,但这是一个工具问题(我一直在与ScalaIDE团队讨论添加一些魔法,但是必须等到它可以获得优先级)。在一个地方定义关于actor的所有属性是重要的部分。
答案 1 :(得分:8)
免责声明:我不是Akka /演员专家。我已经和Actors和Akka一起工作了大约18个月,我仍然试图围绕某些概念,尤其是在不使用Akka时。
对于您想知道Akka未来的返回类型的特定和狭义情况,是的,您应该使用TypedActor。我曾几次使用TypedActors,它们被用来为不属于Actor系统的模块提供API。也就是说,我在Akka之上构建了一个系统,它在Akka网络中完成了大部分工作,但在Akka网络之外有一个或两个模块需要访问Akka网络提供的功能。最引人注目的是Scalatra前端调用了Akka网络,并在响应其客户端之前对Akka网络返回的值进行了一些工作。然而,TypedActor实际上只是Akka网络的前端。我将使用TypedActor作为外部(在Akka网络外部)模块的API前端作为另一个关注点分离。
总的来说,我同意那些告诉你“你不是真的对演员模型”的人试图强迫其回归类型的观点。在最纯粹的形式和我取得最大成功的方式中,Actor模型是使用fire和forget语义实现的。这些消息并不笨拙,在许多情况下,它们帮助组织我的代码并定义工作边界。它确实有助于将它们放入自己的包中。
如果我要实现您所描述的功能,它将如下所示:
trait SomeoneSmarter {
def wouldYouDoMyHomework : Boolean
}
class Response()
case class NoWay() extends Response
case class Sure() extends Response
class ActorNetworkFrontEnd extends Actor {
def receive = {
case d: DoMyHomework =>
busy match {
case true => sender ! NoWay()
case false => sender ! Sure()
}
}
}
case class SomeoneSmarter(actorNetworkFrontEnd:ActorRef) extends SomeoneSmarter {
def wouldYouDoMyHomework : Boolean = {
val future = actorNetworkFrontEnd ? DoMyHomework()
val response = Await.result(future, timeout.duration).asInstanceOf[Response]
response match {
case NoWay() => false
case Sure() => true
}
}
}
请记住我写的那种方式你会做我的家庭作业,它会在等待答案时阻止。然而,有一些聪明的方法可以异步地执行此操作。有关详细信息,请参阅http://doc.akka.io/docs/akka/2.0.3/scala/futures.html。
另外,请记住,一旦您的消息在Akka网络中,您就可以完成所有酷炫的缩放和远程处理,而TypedActor API的用户永远不必知道。
这样做确实会增加大型项目的复杂性,但是如果你认为它分担了向外部模块提供API的责任,甚至可能将责任转移到另一个软件包,那么它很容易管理。
好问题。我迫不及待地想听听更有经验的Akka开发人员的答案。
答案 2 :(得分:1)
Actor的计算模型与面向对象的编程有很大的相似之处。 OO是关于间接转移控制。当您调用方法(发送消息)时,您将失去对消息的控制权。当然,静态编程语言可以帮助您完成所有类型检查的优点,但除此之外您不知道消息会发生什么。地狱,也许方法永远不会返回,虽然返回类型清楚地表明它会(抛出异常,活锁,你的名字......)!同意,当你习惯使用Java甚至更好的Scala时,它会放弃静态类型,但它并不像你没有获得任何好处。动态类型为您提供松耦合。例如,没有必要创建额外的接口只是为了为您的测试引入模拟actor; ActorRef
是您需要的唯一API。