Observer Pattern in Kotlin

JDK provides java.util.Observable and java.util.Observer as an abstract class / interface combination that allows an easy mechanism with which to implement the observer pattern. The framework allows one class to monitor changes in another class and react as needed to the changes. By using the class and interface provided in JDK, we can loosely couple one class to another while still maintaining the observer pattern.

We are going to use three classes in this example: Burger, Bob, and Tina. Bob is a cook who cooks Burgers. Tina is a server who serves the Burgers, and Burger is the object that is passed between Bob and Tina. Bob will notify Tina when a burger is ready, but he doesn’t actually know anything about Tina. All Bob knows (or cares about) is that Tina is an observer.

Let’s begin with both Burger and Bob (since Burger is one line of code).

class Burger(val name: String)

class Bob : Observable() {

    val name = "Bob"

    fun cookBurger(name : String){
        var burger = Burger(name)

        //Call setChanges() prior to calling notifyObservers()
        setChanged() //Inherited from Observable()
        notifyObservers(burger) //Inherited from Observable()
    }
}

We see here that Bob extends Observable. Observable is an abstract class found in the java.util package. In the cookBurger method, Bob creates an new instance of Burger and then calls setChanged(), a method defined in Observable. After calling setChanged(), Bob calls notifyObservers(), which also comes from Observable. All registered Observer objects are notified and since Bob passes the new Burger object as an argument to notifyObservers, they recieve the Burger Bob created.

The Tina class is the receiving end of this pattern. Tina doesn’t know about Bob or any other Observable. She only acts as a receiver for all Observables.

class Tina : Observer{

    val name = "Tina"

    override fun update(o: Observable?, arg: Any?) {
        when (o){
            is Bob -> {
                if (arg is Burger){
                    println("$name is serving the ${arg.name} cooked by ${o.name}")
                }
            }
            else -> println(o?.javaClass.toString())
        }
    }
}

Since Observer is an interface, Tina is forced to implement the update method. The update method has two parameters. The first one is the Observable that is being updated. The other argument is any additional information the Observable passed when calling notifyObservers().

Kotlin distinguishes between nullable and non-null types but Java does not. Anytime we choose to implement Java interfaces or extend abstract classes found in Java, we have to choose if we want null safety or not. There is no way for the Kotlin compiler to know.

In Tina’s case, we have chosen to declare both o and arg as nullable types by adding the question mark (?) after their types. By doing so, we are choosing to accept the null safety offered by the kotlin compiler. We are free to choose otherwise and disregard the null safety, provided we are reasonably sure the types are not going to be null. For example, when I use the Spring framework in Kotlin, I rarely use the null safety because I am usually correct that I am not going to get null from a Spring method.

In this case, I felt it’s better to use the null safety. Observable and Observer are older classes, and we don’t know for sure what may get passed in as parameters to update. Inside of the implementation of update, we have to cast our parameters to the types we need. We can use is operator combined with the when function (see casting). We check if o is Bob and if arg is Burger. When they are, we print out that a Burger is getting served.

There is one final piece to this example. Although Bob and Tina can now work together thanks to Observable and Observer, they need to know about each other. Since Bob extends Observable, he has an addObserver() method that let’s us pass in an instance of Observer.

fun main(args : Array<String>){
    val bob = Bob()
    //Provide Bob and instance of Tina
    bob.addObserver(Tina())

    bob.cookBurger("It takes Bun to Know Bun Burger")
}

Going forward, whenever Bob calls cookBurger, Tina will get notified and serve it.

Putting it Together

Here is the complete program with its output.

package ch4.observer

import java.util.*

class Burger(val name: String)

class Bob : Observable() {

    val name = "Bob"

    fun cookBurger(name : String){
        var burger = Burger(name)

        //Call setChanges() prior to calling notifyObservers()
        setChanged() //Inherited from Observable()
        notifyObservers(burger) //Inherited from Observable()
    }
}

class Tina : Observer{

    val name = "Tina"

    override fun update(o: Observable?, arg: Any?) {
        when (o){
            is Bob -> {
                if (arg is Burger){
                    println("$name is serving the ${arg.name} cooked by ${o.name}")
                }
            }
            else -> println(o?.javaClass.toString())
        }
    }
}

fun main(args : Array<String>){
    val bob = Bob()
    bob.addObserver(Tina())

    bob.cookBurger("It takes Bun to Know Bun Burger")
}

Output

Tina is serving the It takes Bun to Know Bun Burger burger cooked by Bob

Leave a comment