By the end of this lesson, you should be able to...
- Describe:
- the Observer and Mediator patterns
- the software construction problem each is intended to solve
- potential use cases for each (when to use them)
- Assess:
- the suitability of a given design pattern to solve a given problem
- the trade offs (pros/cons) inherent in each
- Implement basic examples of both patterns explored in this class
Review: The After Class coding assignment from previous class.
- Share progress and implementation details with class.
The Observer pattern lets one object observe changes to the state of another object without needing to know the implementation of the object observed.
Observer is comprised of two main objects:
- Subject — The object under observation.
- The subject object allows observer objects to register (and unregister) their interest in receiving updates (notifications) whenever changes are made to the subject, and it automatically notifies observers of any state changes.
- Subjects are responsible for maintaining a list of their dependents (observers).
- Observer(s) — The object(s) doing the observing.
- It is the responsibility of observers is to register (and unregister) themselves on a subject (to get notified of state changes) and to update their state (synchronize their state with subject’s state) when they are notified.
This makes subject and observers loosely coupled - subject and observers have no explicit knowledge of each other.
Observers can be added and removed independently at run-time.
The observer pattern is implemented correctly when an object can receive notifications without being tightly coupled to the object that sends them.
The key to implementing the Observer pattern is to define the interactions between the Subject and Observer objects using protocols. Subject and Observer protocols should contain methods to:
- Add Observers
- Remove Observers
- Notify Observers
Example
// The Observable protocol that will be used by subjects.
protocol Observable {
func addObserver(_ observer:Observer)
func removeObserver(_ observer:Observer)
var observers : [Observer] {get set}
}
// The Observer protocol declares the update method.
protocol Observer: class {
func update(subject: Subject)
}
//Subject implementation
class Subject: Observable {
// Keeping a state in the subject, that observers can access
var state: Int = { return Int.random(in: 0...10) }()
// Subject maintains a list of its observers
var observers = [Observer]()
//Adding an observer
func addObserver(_ observer: Observer) {
print("Subject: Attached an observer.\n")
observers.append(observer)
}
//Removing an observer
func removeObserver(_ observer: Observer) {
if let idx = observers.firstIndex(where: { $0 === observer }) {
observers.remove(at: idx)
print("Subject: Removed an observer.\n")
}
}
// Trigger an update in each observer.
func notifyObservers() {
print("Subject: Notifying observers...\n")
observers.forEach({ $0.update(subject: self)})
}
// The Subject would do some business logic that would trigger the notifications
func someBusinessLogic() {
print("\nSubject: I'm doing something important.\n")
state = Int.random(in: 0...10)
print("Subject: My state has just changed to: \(state)\n")
notifyObservers()
}
}
// Observers react to the updates issued by the Subject they had been subscribed to.
class ObserverA: Observer {
func update(subject: Subject) {
if subject.state < 3 {
print("ObserverA: Reacted to the event.\n")
}
}
}
class ObserverB: Observer {
func update(subject: Subject) {
if subject.state >= 3 {
print("ObserverB: Reacted to the event.\n")
}
}
}
Get into breakout rooms based on the following topics. Take 5-10 min to research as a group, then split into new groups and share your findings with members of other groups:
Topics:
- What are the benefits of the observer pattern?
- What are the pitfalls of the observer pattern?
- When should I use the observer pattern?
There are several examples of the observer pattern in the Cocoa Touch and Cocoa frameworks.
The Cocoa Touch implementation of the Observer pattern that most programmers encounter is in the UI frameworks, where user interactions and changes in UI component state are expressed using events (which are a type of Notification).
Cocoa implements the observer pattern in two ways:
- Notifications
- Key-Value Observing (KVO)
Notifications are based on a subscribe-and-publish model that allows an object (the publisher) to send messages to other objects (subscribers/listeners).
The publisher never needs to know anything about the subscribers.
Notifications are heavily used by Apple.
(See lessons in MOB 1.3 for more on iOS Notifications)
Objective-C has a feature called Key-Value Observing (KVO) that allows one object to receive notifications when the value of another object’s property changes.
It is useful for communicating changes between logically separated parts of your app—such as between models and views.
You can use KVO to communicate between Swift objects as long as both of them are derived from NSObject
, and you use the @objc dynamic
keyword when defining the property that will be observed.
KVO is similar to property observers (willSet
and didSet
), except KVO is for adding observers outside of the type definition.
In Using Key-Value Observing in Swift, Apple outlines KVO implementation in four simple steps:
- Annotate a Property for Key-Value Observing
- Define an Observer class
- Associate the Observer with the Property to Observe
- Respond to a Property Change
Complete this activity to learn how to implement KVO.
Implement the observer pattern manually using this working real-life scenario and turn it into a working app.
The Mediator pattern simplifies peer-to-peer communication between objects by introducing a mediator object that acts as a communications broker between other objects.
Instead of objects communicating directly - and thus requiring knowledge of each other's implementations (i.e., tight coupling) - peer objects send messages to each other via the mediator object.
Mediator joins together colleagues (peers) who share a single interface.
Implementing Mediator typically involves some or all of the following components:
- Colleague protocol - Defines methods and properties each colleague must implement.
- Colleague objects —
Peer objects
that want to communicate with each otherimplement the colleague protocol
.Colleague objects
send messages to and receives messages from other colleagues through an associated mediator object.
- Mediator protocol — Defines methods and properties the mediator class must implement.
- Mediator object -
Implements the mediator protocol.
Coordinates and controls communication between all colleague objects.
- Client object – Creates colleagues and associates an appropriate mediator object with each.
When is the pattern implemented correctly?
- When each object deals only with the mediator and has no direct knowledge of its peers.
Get into breakout rooms based on the following topics. Take 5-10 min to research as a group, then split into new groups and share your findings with members of other groups:
Topics:
- What are the benefits of the mediator pattern?
- What are the pitfalls of the mediator pattern?
- When should I use the mediator pattern?
Mediator Pattern Video Explanation
Follow along this video to implement the pattern
Things to think about from the coding sample:
- Looks like there are objects strongly referencing each other. What can be changed to prevent this?
- What if there's more demand of requests of a certain concentration and limited TAs available? Can we give them a limit of requests?
- It's possible to keep refactoring this solution. What about another protocol for the Peer classes? or a Base Class?
- Can the Request class be a Struct?
- Look up these other Behavioral Patterns:
- Visitor
- Iterator
- Memento
- Strategy
- Research the following concepts:
- The Lapsed Listener Problem
NSKeyValueObserving
and itsaddObserver
functionNSKeyValueChangeKey
NSKeyValueObservingOptions
- Continued Stretch Challenge: Extend the Media Player app you created in the previous class by implementing the following using either the Observer or the Mediator pattern:
- add a notification that, when the video clip is done playing, sends the user this message: "Your media file is done playing — do you want to replay it?"
- Analyze the following simple implementation of the Observer pattern (KVO).
- For discussion in next class, pay particular attention to the
.observe(_:_)
function, especially the implications of its origin (where does it come from?) and purpose.
import UIKit
@objc class Person: NSObject {
@objc dynamic var name = "Peter Gene Bayot Hernandez"
}
let peter = Person()
print(peter.name)// prints ""Peter Gene Bayot Hernandez"
peter.observe(\Person.name, options: .new) { person, change in
print("I'm now called \(person.name)")
}
peter.name = "Bruno Mars" // prints "I'm now called Bruno Mars"