本例将表示几何图形的一组类的实例发送给actor,该actor将这组实例绘制到显示屏上。像是渲染工厂(rendering farm)在为动画生成场景。一旦场景渲染完毕,构成的场景的几何图形便会被发送给某一actor进行展示。
In [1]:
case class Point(x: Double = 0.0, y: Double = 0.0)
abstract class Shape() {
// draw方法接受一个函数参数
// 每个图形对象都会将自己的字符格式传给函数f,由f完成绘制工作
def draw(f: String => Unit): Unit = f(s"draw: ${this.toString}")
}
case class Circle(center: Point, radius: Double) extends Shape
case class Rectangle(lowerLeft: Point, height: Double, width: Double) extends Shape
case class Triangle(point1: Point, point2: Point, point3: Point) extends Shape
样本类(case class)让编译器自动生成很多方法,比如String、equals和hashCode方法。编译器还会为样本类同时生成一个伴生对象。
In [2]:
// Scala调用生成的equals方法来判断==这样的逻辑比较
val p20 = new Point(2.0)
val p20b = new Point(2.0)
val p02 = new Point(y = 2.0)
In [3]:
p20 == p02
In [4]:
p20 == p20b
In [5]:
// 使用Point伴生类的apply方法
val p1 = Point.apply(1.0, 2.0)
Point.apply是构造Point对象的工厂方法,调用该方法就好像不通过new关键字调用Point的构造器一样。
object Point {
def apply(x: Double = 0.0, y: Double = 0.0) = new Point(x, y)
...
}
以上定义好了形状类型,接下来回到actor上
In [6]:
object Messages {
object Exit
object Finished
case class Response(message: String)
}
In [7]:
load.jar("D:\\scala\\lib\\akka-actor_2.11-2.3.10.jar")
In [8]:
import akka.actor.Actor
此处定义了一个actor类,用于绘制图形。实现了抽象方法Actor.receive,该方法是Actor的子类必须实现的方法,定义了如何处理接收到的消息。
In [9]:
class ShapesDrawingActor extends Actor {
import Messages._
def receive = {
case s: Shape =>
s.draw(str => println(s"ShapesDrawingActor: $str"))
sender ! Response(s"ShapesDrawingActor: $s drawn")
case Exit =>
println(s"ShapesDrawingActor: exiting...")
sender ! Finished
case unexpected => //default. Equivalent to "unexpected: Any"
val response = Response(s"Error: Unknown message: $unexpected")
println(s"ShapesDrawingActor: $response")
sender ! response
}
}
receive是偏函数,接受单一的Any类型参数并返回Unit值。由于该函数返回Unit对象,因此函数体一定会产生副作用。由于actor系统采用了异步消息机制,它必须依靠副作用。通常由于传递消息后无法返回任何消息,代码块中便会发送一些其他消息,包括给发送者的返回消息。
偏函数中仅包含了一些case子句,对传递给函数的消息执行模型匹配。receive方法会尝试将接受到的各条消息与编写的模式匹配表达式进行匹配,并执行最先被匹配上的表达式。
Actor.sender函数返回了actor发送消息接收方的对象引用,而!方法用于发送异步消息。
In [10]:
import akka.actor.{Props, ActorRef, ActorSystem}
In [11]:
object Start
In [21]:
class ShapesDrawingDriver(drawerActor: ActorRef) extends Actor {
import Messages._
def receive = {
case Start =>
drawerActor ! Circle(Point(0.0, 0.0), 1.0)
drawerActor ! Rectangle(Point(0.0, 0.0), 2, 5)
drawerActor ! 3.14159
drawerActor ! Triangle(Point(0.0, 0.0), Point(2.0, 0.0), Point(1.0, 2.0))
drawerActor ! Exit
case Finished =>
println(s"ShapesDrawingDriver: cleaning up...")
context.system.shutdown()
case response: Response =>
println("ShapesDrawingDriver: Response =" + response)
case unexpected =>
println("ShapesDrawingDriver: ERROR: Receive an unexpected message =" + unexpected)
}
}
接下来是驱动应用的主方法,首先构建一个akka.actor.ActorSystem对象和两个actor对象,然后将ShapesDrawingActor对象的引用传递给ShapesDrawingDriver,最后向驱动对象发送Start命令,启动应用。
In [22]:
object ShapesDrawingApp extends App{
val system = ActorSystem("DrawingActorSystem")
val drawer = system.actorOf(Props(new ShapesDrawingActor), "drawingActor")
val driver = system.actorOf(Props(new ShapesDrawingDriver(drawer)), "drawingService")
driver ! Start
system.shutdown
}
最后结果如图: