Sunday, October 10, 2010

Scala exercise 2: observer design pattern

Introduction


This is the second exercise in my Scala exercises series. If you haven't seen it before, you may want to start from exercise 1: template method. Below is exercise 2: observer design pattern.

Problem: Complete a Scala trait Observed (shown below) to represent the subject being observed and the Scala trait Observer to represent an observer. The Observed object allows one or more Observers to register with it. Later, it can fire an event and notify all such Observers. The code is like (where E is the type of the event to be fired):


trait Observed[E] {
def addObserver(o: Observer[E]) ...
def notifyObservers(ev: E) ...
}

trait Observer[E] {
def eventOccurred(ev: E)
}

Then use these traits to implement Java bean "bounded properties", e.g., to allow others to get notified when properties of a Book instance is changed:

//Let others observe changes to its properties
case class Book(var title: String, var price: Double) extends Observed[PropertyChangeEvent] {
def setTitle(title: String) {
val oldTitle = this.title
this.title = title
//Notify the observers
...
}
def setPrice(price: Double) {
val oldPrice = this.price
this.price = price
//Notify the observers
...
}
}

//A sample observer class
class Foo extends Observer[PropertyChangeEvent] {
//Just print some info after a property has been changed
def eventOccurred(ev: PropertyChangeEvent) = {
printf("Foo: %s of %s has changed from %s to %s\n", ev.getPropertyName, ev.getSource, ev.getOldValue, ev.getNewValue)
}
}

object BeanTest {
def main(args: Array[String]) {
val b1 = new Book("Scala programming", 35.95)
val foo = new Foo
b1.addObserver(foo) //Register the observer
b1.setTitle("Thinking in Scala")  //foo should get an event
b1.setPrice(39.95) //ditto
b1.setTitle("Effective Scala") //ditto
}
}

The above code should print:
Foo: title of Book(Thinking in Scala,35.95) has changed from Scala programming to Thinking in Scala
Foo: price of Book(Thinking in Scala,39.95) has changed from 35.95 to 39.95
Foo: title of Book(Effective Scala,39.95) has changed from Thinking in Scala to Effective Scala

Try to do it now! Then, click here to see the answer.

4 comments:

  1. You using Scala to code like you would do it in Java. Should move to functional style. Hint: Observable doesn't have to be a trait, since it only has 1 method. It is a function

    ReplyDelete
  2. Defining both the Observer and the Observed as traits allows for a more decoupled solution. Instead of extending in the classes you could use the "with" keyword at declaration time of the variables. This allows users to decide when a trait is appropriate and when to "cut the fat" from the classes. This would result in:
    val b1 = new Book("Scala programming", 35.95) with Observed[PropertyChangeEvent]
    val foo = new Foo with Oberver[PropertyChangeEvent]
    I also wanted to mention that the function style Eugene mentions is good but Scala is a hybrid of functional and object oriented styles. Personally I would stick with one or the other until I'm more comfortable with the code and its quirks. I understand OO better at the moment so I would tend to do things in that manner.

    ReplyDelete
  3. Hi Eugene,
    Thanks for the comment. In fact, I thought about using a function instead of a trait. Then, the signature of addObserver() would become:
    def addObserver(observer: (ev: E) => Unit)
    which is telling much about when the observer (the function) will be called. In contrast, the trait tells clearly that it is called after the event has occurred:
    def eventOccurred(ev: E)

    ReplyDelete
  4. Hi Chris,
    Thanks for pointing out the "with" use cases!

    ReplyDelete