The Observer Pattern
Observer Pattern
Contextual Forces
Motivation
An event can occur and a number of entities need to receive a message about it. When the event will occur, and perhaps if it will occur at all is unpredictable. Also, the number of entities, and which entities they are, is unpredictable. Ideally, the subscribers to this notification should be configurable at runtime.
From the Gang of Four: "Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically"
Encapsulation
Using the Observer Pattern encapsulates the relationships between the Observed entity and the Observers. The number of Observers that exist at any point during runtime is also encapsulated. Depending on implementation, the information being provided to the Observers when the event occurs may also be encapsulated.
Procedural Analog
The procedural mechanism often used to produce the same effect at the Observer is to maintain an array of function pointers as data, and then to iterate over them in a loop when the circumstance occurs that needs to be handled by more than one routine. Often this requires boundary checking on the array, and null pointer checks on the function pointers, to ensure the system is robust.
Non-Software Analog
Magazines and newspapers are delivered on a regular basis to all their subscribers. The number of subscribers and who they are will change over time as new customers pay for a subscription, and as existing customers allow their subscriptions to lapse. While the publication of such a periodical is often quite predictable, there may be special editions or other unpredictable instances of the publication that will be sent out to all subscribers. The publisher, therefore, maintains a list of all subscribers, which can be easily added to and removed from, and when the publication is ready for distribution, will use this list to send a copy to all those who are currently subscribed.
Not only is this a non-software analog to the Observer Pattern, it is also an alternate name for the pattern: Publish-Subscribe.
Implementation Forces
Example
public interface Observer { public notify(String acct, double amt); } public class Account { private String acct; private double balance; private Arraylist myObservers; public Account(String anAcct, double aBalance) { acct = anAcct; balance = aBalance; myObservers = new Arraylist(); } public void addObserver(AccountObserver o){ myObservers.add(o); } public void removeObserver(AccountObserver o) { myObservers.remove(o); } public void credit(double amt) { balance += amt; } public void debit(double amt) { balance -= amt; if(balance < 0) overdrawn(); } private void overdrawn() { foreach(Observer o in myObservers) { o.notify(acct, balance); } } } public Audit implements Observer { public void notify(String auditedAccount, double overdrawnAmount) { // write info to log, take other appropriate action } } public class Application { public static void main (String args[]) { Account anAccount = new Account ("XYZ123", 10.00); Audit anAudit = new Audit(); // Register observer with account anAccount.addObserver(anAudit); // Cause account to be overdrawn anAccount.debit(11.00); } }
Questions, concerns, credibility checks
The Observer Pattern abstracts the actual observers by using an interface or abstract class that hides their specific types. This is necessary for the pattern to deliver its true value, because this allows new observers to be created and registered flexibly. However, this also means that all observers must be able to respond to the same Notify() method in their interface, which is a credibility check on the pattern.
If diffrent observers require different information at notification time, there are ways to accomodate this without breaking the fundamental encapsulation of the pattern. See Options in Implementation below.
Questions to ask when considering the use of an Observer Pattern include:
- How consistently will the Observers be notified, one to another?
- What information is to be given when the event occurs?
- Do the Observers want to know the event for a given instance or for all objects of a class?
- How consistent is the triggering event for the notification of the Observers?
- Should there be multiple "addObserver" methods for different triggering events?
- The ratio of subjects to Observers, as a performance issue?
- Should this be constrained to a maximum amount?
- What action should be taken if this amount is exceeded?
- What's the speed of the connection between the observer and the observed, as a performance issue?
- What's the latency between when the event occurs and the Observers are notified, and is this critical?
Options in implementation
In some cases it is also possible to create abstract coupling between the Observers and the observed object, by using an abstract class or interface for the observed:
The advantage to this is that the Observer(s) may be reused and assigned to observer other entities derive from this same type.
Sometimes the Observers that will subscribe for notification will require different information about the event when it occurs. For example:
Here we have three different implementation of the Observer interface, but arguably CustomerNotification will need an email address or other information from the Account object, and OverdraftProtection may need a second account number, to draw funds from in covering the overdraft.
This means that the interface notify(String acct, double amt), which was sufficient for the Audit object, does not provide enough information for these new Observers.
We have at least 3 ways to solve this problem:
1. We could expand the interface to include paramters for all the information that any Observer could possibly want, and then the various Observers could ignore those parameters they were not concerned with. It is a good idea, in this case, to name the unneeded parameters with the word "ignore" prepended to them, to make this clear. For example:
public interface Observer { public notify(String acct, double amt, String emailAddr, String overdraftAcct); } public Audit implements Observer { public void notify(String auditedAccount, double overdrawnAmount, String ignoreEmailAddr, String ignoreOverdraftAcct) { // write info to log, take other appropriate action } }
While this can solve the problem, it often produces long parameter lists and thus creates excess complexity and can degrade performance.
2. We can expand the interface of the observed object (Account, in this case) and allow the Observers to call back for the information they require:
Each Observer implementation can call back on the methods that interest it. The costs here include:
- The expanded interface on the observed object (which may not be desireable).
- More method calls, which may cause problem especially if the observed entity and one or more Observers are in different processes, or on different sides of a network boundary.
- The Observers couple directly to the concrete class they observe, which reduces their resuability.
3. An association object, or data class, can be passed from the observed object to the Observers:
This requires the creation of another object, but it allows the various Observers to consume different information without calling back upon, or concretely coupling to, the entity being observed.
Consequent Forces
Testing issues
Testing the observed entity: A Mock Observer can be registered with, in the case above, the Account object, and then the Account can be placed into an overdrawn condition by the test, and the Mock can report whether or not it was called with the correct parameters.
Testing the Observers: The testability of the the Observers depends on their implementation. If they are implemented to make callbacks on the observed entity, the entity can be mocked or faked to record and report the callbacks. If the Observers do not make callbacks, then they must be testing in traditional ways, depending on other objects they interact with in the system.
Cost-Benefit (gain-loss)
- The pattern provides abstract coupling from the observed entity to its Observers, and may provide abstract coupling in both directions
- Allows for broadcast communication to an unpredictable number of receivers
- Supports unexpected updates and/or events
- Provides the Open-Closed principle for relationships