The Observer Design Pattern is a behavioral design pattern, which consists of two key components:
- Subject
- Observer
OBSERVER
With a simple real-life example, someone looking at the traffic light is an observer, and he waits for the light to turn green, so he can go.THE SUBJECT
On the other hand, the subject is the traffic light, which “emits”, in this case, 3 signals for an observer to follow. Red means stop, yellow to prepare and green to go!The subject maintains it’s list of observers, and when a state changes, it notifies them all about the change.
Quick code snippet of Simple Observer Protocol:
protocol ObserverProtocol {
var id: Int { get set }
func onTrafficLightColorChange(_ newColor: UIColor)
}
This Protocol requires the implementation of an id property and a method to handle the change.
final class VehicleObserver: ObserverProtocol {
var id: Int
required init(id: Int) {
self.id = id
}
func onTrafficLightColorChange(_ newColor: UIColor) {
switch newColor {
case .red:
debugPrint("Stop!")
case .yellow:
debugPrint("Get ready!")
case .green:
debugPrint("Go!")
default:
debugPrint("Unhandled case for: \(Self.self)")
}
}
}
This VehicleObserver class conforms to ObserverProtocol and handles the logic, in the case above prints out commands for each newColor value.
In the next example, there is a new class called VendorObserver, which also conforms to the ObserverProtocol and implements the logic for the traffic light change event. This class will also print out commands for each value received.
final class VendorObserver: ObserverProtocol {
var id: Int
required init(id: Int) {
self.id = id
}
func onTrafficLightColorChange(_ newColor: UIColor) {
switch newColor {
case .red:
debugPrint("Start selling products!")
case .green:
debugPrint("Stop selling products!")
default:
debugPrint("Unhandled case for: \(Self.self)")
}
}
}
This VendorObserver example is a bit strange, especially because it also reacts to the change of a traffic light, but this is just to quickly showcase a point on how a single subject can notify different observers and how can they react.
Now, on to the Subject
Definition of a protocol for the "Subject", with an example by using POP (Protocol-oriented-programming)
protocol TrafficLightSubjectable {
var color: UIColor { get set }
var observers: [ObserverProtocol] { get set }
var privateQueue: DispatchQueue { get }
func addObserver(_ observer: ObserverProtocol)
func removeObserver(_ observer: ObserverProtocol)
func notifyObservers()
}
This protocol defines a set of rules that Traffic light subject should follow. He will contain info on the color, collection of observers, a private serial queue, just for the sake of serialization of adding, removing and notifying observers, and the methods to add, remove and notify observers.
Subject Implementation:
final class TrafficLightSubject: TrafficLightSubjectable {
var color: UIColor = .red {
didSet {
notifyObservers()
}
}
var observers: [ObserverProtocol] = []
let privateQueue: DispatchQueue = DispatchQueue(label: "com.codenameblackmesa.observerpatternQueue1")
func addObserver(_ observer: ObserverProtocol) {
privateQueue.sync {
let alreadyContains = observers.contains(where: { $0.id != observer.id })
guard observers.isEmpty || alreadyContains else { return }
observers.append(observer)
}
}
func removeObserver(_ observer: ObserverProtocol) {
privateQueue.sync {
observers.removeAll(where: { $0.id == observer.id })
}
}
func notifyObservers() {
privateQueue.sync {
observers.forEach {
$0.onTrafficLightColorChange(color)
}
}
}
deinit {
observers.removeAll()
}
}
Breaking down the logic, there is a color property, by default set to .red. On didSet{} method, notifyObservers() method is called. The TrafficLightSubject also have observers property, which is a collection of observer protocols that are defined earlier, and a private serial queue, which will be used to serialize calls to observers array. All the methods are self-explanatory (add/remove observers from an array), and a method to iterate through all observers and notify them when a color change occurs.
Using the Subject, and the 2 observers to see how to call them in the example:
let subject = TrafficLightSubject()
let vehicleObserver = VehicleObserver(id: 1)
let vendorObserver = VendorObserver(id: 2)
subject.addObserver(vehicleObserver)
subject.addObserver(vendorObserver)
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
subject.color = .yellow
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
subject.color = .green
}
To Recap the final snippet
First, a subject “traffic light” is created. Next, the 2 observers with their own “unique” ids. Then, they are both added to the subject. And finally, with the asyncAfter method, a simulated "semaphore" changes the traffic light after 2 seconds to .yellow, and then to .green. In the console output, on a change to .yellow, VehicleObserver will print out
"Get ready!" "Unhandled case for: VendorObserver"When a traffic light changes to .green, the output will be like:
"Go!" "Stop selling products!"
Also, check out this article, provided by CodeCat15 @ Medium, https://codecat15.medium.com/observer-design-pattern-in-swift-392b98f0479a
Comments
Post a Comment