2
votes

I am fairly new to Scala but have encountered a sort of a coding pattern from the code I've seen so far. Here is an example of what I am talking about:

object TicketSeller {
  def props(event: String) = Props(new TicketSeller(event))

  case class Add(tickets: Vector[Ticket])

  case class Buy(tickets: Int)

  case class Ticket(id: Int)

  case class Tickets(event: String,
                     entries: Vector[Ticket] = Vector.empty[Ticket])

  case object GetEvent

  case object Cancel

}

class TicketSeller(event: String) extends Actor {

  import TicketSeller._

  var tickets = Vector.empty[Ticket]

  def receive = {
    case Add(newTickets) => tickets = tickets ++ newTickets
    case Buy(nrOfTickets) =>
      val entries = tickets.take(nrOfTickets).toVector
      if (entries.size >= nrOfTickets) {
        sender() ! Tickets(event, entries)
        tickets = tickets.drop(nrOfTickets)
      } else sender() ! Tickets(event)
    case GetEvent => sender() ! Some(BoxOffice.Event(event, tickets.size))
    case Cancel =>
      sender() ! Some(BoxOffice.Event(event, tickets.size))
      self ! PoisonPill
  }
}

Notice how the TicketSeller class imports all things from the TicketSeller object

  import TicketSeller._

I have seen this "object in class" pattern before in which most if not all of the things in the object are case classes. Why is it done like this? What are the benefits and is it really a pattern or am I just not deep enough into things to fully understand?

Thank's a bunch!

1

1 Answers

1
votes

If you're coming from Java background, static members will be the closest analogy.

In case of classes, a nested class in a class may have an implicit reference to the owner object; additionally, it can access the enclosed object's fields. In case of a class within the object, you have no such risk, therefore much less chances for an error.

Similarly, everything you define in the class, can only be tested as part of that class's instance. So your tests will need to instantiate the class, coming up with some value for event (which will not make much sense for most of the tests, adding to the clutter), whereas with object members you just refer to whatever members are there — the state they have access to is very limited, and the state they need to be instantiated is already there.

PS. Props(new TicketSeller(event)), by the way, makes another great example. The way it is done here is completely fine, but if you move it to the class itself, it becomes a dangerous variant for exactly the same reason: visibility of the enclosing class's state makes it way too easy to close over the enclosing scope.

// NOT RECOMMENDED within another actor:
// encourages to close over enclosing class
val props7 = Props(new MyActor)

This method is not recommended to be used within another actor because it encourages to close over the enclosing scope, resulting in non-serializable Props and possibly race conditions (breaking the actor encapsulation). We will provide a macro-based solution in a future release which allows similar syntax without the headaches, at which point this variant will be properly deprecated. On the other hand using this variant in a Props factory in the actor’s companion object as documented under “Recommended Practices” below is completely fine.