DEV Community

Saurabh Kishore Tiwari
Saurabh Kishore Tiwari

Posted on

Observer Pattern in Scala

Observer pattern has two main components — Subject and Observer. All observers subscribe to the subject, and in case of any kind of change, the subject is responsible for propagating that information to all observers. As you may have guessed, it sounds like a one-to-many dependency between objects. Let’s have a look at the UML diagram to get a better understanding.

Image description

Let’s breakdown the structure now

Subject

  1. Maintains a collection of all the observers.
  2. Provides an interface to attach and detach the observers.

Observer

  1. Defines an updating interface for objects that should be notified of changes in the subject.

ConcreteSubject

  1. Stores the state of interest to ConcreteObserver.
  2. Sends a notification to its observers when it’s state changes.

ConcreteObserver

  1. Maintains a reference to ConcreteSubject.
  2. Might store state that should stay consistent with the subject’s state.
  3. Implements the Observer updating interface to keep its state consistent with the subject.

Implementation

Consider a stock company that tells its users the real-time value of all the stocks. The subject will be the stock company, and all the users who subscribe to it will be its observers.

CODE

User.scala

sealed trait Observer {
  def publishUpdate(stocks: mutable.Map[Int, Stock]): Unit
}

case class User(name: String, uuid: String) extends Observer:
  def publishUpdate(stocks: mutable.Map[Int, Stock]): Unit =
    println(name + " " + stocks.values.map(x => x.name + " " + x.price))

Enter fullscreen mode Exit fullscreen mode

StockCompany.scala

case class Stock(id: Int, name: String, price: Double)

sealed trait Subject {
  def registerNewUser(user: User): Unit
  def notifyUser(): Unit
  def deleteUser(user: User): Unit
  def registerStocks(stock: Stock): Unit
  def updateStockPrice(stock: Stock): Unit
}

object StockCompany extends Subject:
  private val stocks = mutable.TreeMap[Int, Stock]()
  private val users = mutable.TreeMap[String, User]()

  def registerNewUser(user: User): Unit =  // Attach
    users.put(user.uuid, user)
    user.publishUpdate(stocks)             // As soonas a user registers they get the live prices

  def notifyUser(): Unit =                 // Notify
    users.foreach(_._2.publishUpdate(stocks))

  def deleteUser(user: User): Unit =       // Detach
    users.remove(user.uuid)

  def registerStocks(stock: Stock): Unit =
    stocks.put(stock.id, stock)
    notifyUser()

  def updateStockPrice(stock: Stock): Unit =
    stocks.put(stock.id, stock)
    notifyUser()
Enter fullscreen mode Exit fullscreen mode

MainRunner.scala

object MainRunner:
  def main(args: Array[String]): Unit =
    val user1 = User("user1", "ADBPR4561E")
    val user2 = User("user2", "BFTPD3461S")

    val stock1 = Stock(1, "stock1", 23.42)
    val stock2 = Stock(2, "stock2", 34.53)
    val stock3 = Stock(3, "stock3", 45.64)

    StockCompany.registerStocks(stock1)
    StockCompany.registerStocks(stock2)

    StockCompany.registerNewUser(user1)
    Thread.sleep(1000)
    StockCompany.registerNewUser(user2)
    Thread.sleep(1000)
    StockCompany.registerStocks(stock3)
    Thread.sleep(1000)
    StockCompany.updateStockPrice(Stock(3, "stock3", 123.45))

Enter fullscreen mode Exit fullscreen mode

Known Uses and Related Patterns

This pattern can be used in any scenario, when the user has to be notified in case of any event/change occurs, like:

  1. Mobile app for weather update
  2. Stock Prices getting updated live on WebPages
  3. Ticket confirmation
  4. Notification from any application
  5. Following a user on Instagram 😂
  6. “Notify me” when available on e-commerce sites
  7. Event listener on a button

Now, if you give it a thought, the StockCompany has to take care of a lot of things. If we can somehow segregate the task and add another layer of abstraction (say a PriceManager) which will take care of notifying all the users, the stock company will just have to produce the prices to Price Manager and users will subscribe to Price manager while the Price manager orchestrates everything. This is kind of like a Publisher-Subscriber model(can be called a mediator model too based on the implementation)

Top comments (0)