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.
Let’s breakdown the structure now
Subject
- Maintains a collection of all the observers.
- Provides an interface to attach and detach the observers.
Observer
- Defines an updating interface for objects that should be notified of changes in the subject.
ConcreteSubject
- Stores the state of interest to ConcreteObserver.
- Sends a notification to its observers when it’s state changes.
ConcreteObserver
- Maintains a reference to ConcreteSubject.
- Might store state that should stay consistent with the subject’s state.
- 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))
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()
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))
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:
- Mobile app for weather update
- Stock Prices getting updated live on WebPages
- Ticket confirmation
- Notification from any application
- Following a user on Instagram 😂
- “Notify me” when available on e-commerce sites
- 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)