Scala By Example: 拍卖会

来源:互联网 发布:vue2.0数据绑定原理 编辑:程序博客网 时间:2024/05/16 00:37

Scala的最大亮点就是Erlang风格的actor处理机制。但是第二个示例就涉及到Actor令我始料未及。不过这本书在Scala官网上是属于“进阶示例”,所以出手有些快也在所难免。(如果看不懂这个例子,最好先读铅笔书《Scala程序设计》(我假设你懂Java,否则你就直接选Erlang了,不是吗。书非常薄,如果有闭包之类的经验的话,三天就能看个差不多)。

   1: #!/bin/env scala
   2: !#
   3:  
   4: import scala.actors._
   5: import Actor._
   6: import java.util.Date
   7:  
   8: // for each base class, there are a number of 'case classed' which define the
   9: // format of marticular messages in the class
  10: abstract class AuctionMessage
  11: case class Offer(bid: Int, client: Actor) extends AuctionMessage
  12: case class Inquire(client: Actor) extends AuctionMessage
  13:  
  14: abstract class AuctionReply
  15: case class Status(asked: Int, expire: Date) extends AuctionReply
  16: case object BestOffer extends AuctionReply
  17: case class BeatenOffer(maxBid: Int) extends AuctionReply
  18: case class AuctionConcluded(seller: Actor, client: Actor) extends AuctionReply
  19: case object AuctionFailed extends AuctionReply
  20: case object AuctionOver extends AuctionReply
  21:  
  22: class Auction(seller: Actor, minBid: Int, closing: Date) extends Actor {
  23:     val timeToShutdown = 3600
  24:     val bidIncrement = 10
  25:  
  26:     println("Auction Started!")
  27:     def act() {
  28:         var maxBid = minBid - bidIncrement
  29:         var maxBidder: Actor = null
  30:         var running = true
  31:         while(running) {
  32:             println("Auction is still on")
  33:             // the 'receiveWithin' method of class Actor takes as parameters a
  34:             // time span given in milliseconds and a function that process
  35:             // messages in the mailbox.
  36:             val timeout = closing.getTime() - new Date().getTime()
  37:             println("To be timeout in " + (timeout / 1000) + "s")
  38:             receiveWithin(timeout) {
  39:                 case Offer(bid, client) =>
  40:                     if(bid >= maxBid + bidIncrement) {
  41:                         if(maxBid >= minBid) maxBidder ! BeatenOffer(bid)
  42:                         maxBid = bid; maxBidder = client; client ! BestOffer
  43:                     } else {
  44:                         client ! BeatenOffer(maxBid)
  45:                     }
  46:                 case Inquire(client) => client ! BeatenOffer(maxBid)
  47:                 // if no messages are received in the meantime, this pattern is
  48:                 // triggered after the time span which is passed as argument to
  49:                 // the enclosing receiveWithin method.
  50:                 case TIMEOUT =>
  51:                     if(maxBid >= minBid) {
  52:                         val reply = AuctionConcluded(seller, maxBidder)
  53:                         maxBidder ! reply
  54:                         seller ! reply
  55:                     } else {
  56:                         seller ! AuctionFailed
  57:                     }
  58:                     receiveWithin(timeToShutdown) {
  59:                         case Offer(_, client) => client ! AuctionOver
  60:                         case TIMEOUT => running = false
  61:                     }
  62:             }
  63:         }
  64:     }
  65: }
  66:  
  67: val seller = actor {
  68:     println("I'm the seller")
  69:     var running = true
  70:     while(running) {
  71:         println("Seller is still waiting...")
  72:         receiveWithin(5000) {
  73:             case AuctionConcluded(_, maxBidder) =>
  74:                 println("Auction concluded! The max bidder is" + maxBidder)
  75:                 running = false
  76:             case AuctionFailed =>
  77:                 println("F**k, it failed again!")
  78:                 running = false
  79:             case _ => println("Nothing happened")
  80:         }
  81:     }
  82: }
  83:  
  84: val date = new Date(new Date().getTime() + 20000)
  85: val auction = new Auction(seller, 100, date)
  86:  
  87: val bidder1 = actor {
  88:   auction ! Inquire(self) // notice: auction is not yet started
  89:   receiveWithin(1000) { case msg => println(msg) }
  90: }
  91:  
  92: auction.start()
  93:  
  94: val bidder2 = actor {
  95:   auction ! Offer(120, self)
  96:   receiveWithin(1000) { case msg => println(msg) }
  97: }
  98:  
  99: val bidder3 = actor {
 100:   auction ! Offer(150, self)
 101:   receiveWithin(1000) { case msg => println(msg) }
 102: }
 103:  
 104: val bidder4 = actor {
 105:   auction ! Offer(120, self)
 106:   receiveWithin(1000) { case msg => println(msg) }
 107: }
 108:  
 109: receiveWithin(30000) { case _ => } // little trick:)

注意,在程序的一开始,我们看到两个什么也不做的抽象类和一堆派生的case类/对象,它们就是在Actor之间传递的消息。当然,也可以不定义这些类和对象,case同样可以接受元组形式的参数。注意,使用Scala的时候,你可以彻底忘掉Singleton(也许有人还在争论Singleton要不要做双重加锁呢),那几个用object声明的对象就是Scala中的单例对象。(有人曾经评价ruby这样的魔幻语言让许多经典模式都瞬时凌乱,我认为Groovy和Scala都是如此)

第二个重点则是act()的方法。尤其重要的是receiveWithin和TIMEOUT的配合使用。想想看,用JAVA来写的话你需要写多少行?

原书的代码只包含了类的定义,为了运行这些代码,我加了N多行。大家可以运行这个程序看看输出是什么样的顺序。体会一下Scala消息传递的“风格”。

最后需要指出的是最后一行代码。逻辑上这行代码什么都不做,但是由于所有Actor(包括auction, seller 和一群bidder)都在自己的进程里运行,所以当bidder4被创立之后,主进程就结束了(而此时拍卖会并没有结束)。因此,加上一行等待专用码还是有必要的。

PS. 虽然我以说一口华丽的中文为骄傲,但是当写代码的时候,输入中文永远令我纠结。今天我下定决心,以后在IDE里也好、VIM里也好,都用鸟语书写了。(嗯,顺便把练字放到我的日程里,想当年我的书法还是很漂亮的)

PS II. 虽然把Scala的语法大致过了遍,真正写程序的时候还是不熟练,所以我做了个练习项目,用主要Scala来写,不会的地方用Groovy和Java搭一把。结果Scala和Java相互调用没有问题,Groovy和Java相互调用也没有问题,放到一起就杯具了。Groovy可以调用Scala,但是混合编译的时候要先编译Groovy后编译Scala,否则就报错,至于反过来,完全没法混合编译(IDEA和gradle都一样),看来只有把不同的代码分别编译后才能相互调用了。

PS III. 一想到以后不用再输入synchronized就兴奋。

Technorati 标签: Scala