My approach is to look in the problem for clues as to what is varying. Then, I attempt to encapsulate the variation. In the current case, I find:
Different kinds of objects— There is a list of objects that need to be notified of a change in state. These objects tend to belong to different classes.
Different interfaces— Since they belong to different classes, they tend to have different interfaces.
First, I must identify all of the objects that want to be notified. I will call these the observers since they are waiting for an event to occur.
I want all of the observers to have the same interface. If they do not have the same interface, then I would have to modify the subject—that is, the object that is triggering the event (for example, Customer), to handle each type of observer.
By having all of the observers be of the same type, the subject can easily notify all of them. To get all of the observers to be of the same type,
In Java, I would probably implement this with an interface (either for flexibility or out of necessity).
In C++, I would use single inheritance or multiple inheritance, as required.
In most situations, I want the observers to be responsible for knowing what they are to watch for and I want the subject to be free from knowing which observers depend on it. To do this, I need to have a way for the observers to register themselves with the subject. Since all of the observers are of the same type, I must add two methods to the subject:
attach(Observer)— adds the given Observer to its list of observers
detach(Observer)— removes the given Observer from its list of observers
Now that the Subject has its Observers registered, it is a simple matter for the Subject to notify the Observers when the event occurs. To do this, each Observer implements a method called update. The Subject implements a notify method that goes through its list of Observers and calls this update method for each of them. The update method should contains the code to handle the event.
But notifying each observer is not enough. An observer may need more information about the event beyond the simple fact that it has occurred. Therefore, I must also add method(s) to the subject that allow the observers to get whatever information they need. Figure 17-2 shows this solution.
In Figure 17-2, the classes relate to each other as follows:
The Observers attach themselves to the Customer class when they are instantiated. If the Observers need more information from the subject (Customer), the update method must be passed a reference to the calling object.
Each Observer calls getState for information on the newly added Customer to see what it needs to do. Note: Typically, there would be several methods to get the needed information.
Note in this case, we use static methods for attach and detach because observers want to be notified for all new Customers. When notified, they are passed the reference to the Customer created.
Example 17-1 shows some of the code required to implement this.
This approach allows me to add new Observers without affecting any existing classes. It also keeps everything loosely coupled. This organization works if I have kept all of the objects responsible for themselves.
How well does this work if I get a new requirement? For example, what if I need to send a letter with coupons to customers located within 20 miles of one of the company's “brick and mortar” stores.
// Note: Do not actually use the name Observer // as that is a Java class in java.util class Customer { static private Vector myObs; static { myObs= new Vector(); } public static void attach(Observer o){ myObs.addElement(o); } public static void detach(Observer o){ myObs.remove(o); } public String getState () { // have other methods that will give the // required information } public void notifyObs () { for (Enumeration e = myObs.elements(); e.hasMoreElements() ;) { ((Observer) e).update(this); } } } abstract class Observer { public Observer () { Customer.attach( this); } abstract public void update(Customer myCust); } class AddrVerification extends Observer { public AddrVerification () { super(); } public void update ( Customer myCust) { // do Address verification stuff here // can get more information about customer // in question by using myCust } } class WelcomeLetter extends Observer { public WelcomeLetter () { super(); } public void update (Customer myCust) { // do Welcome Letter stuff // here can get more // information about customer // in question by using myCust } } |
To accomplish this, I would simply add a new observer that sends the coupon. It only does this for new customers living within the specified distance. I could name this observer BrickAndMortar and make it an observer to the Customer class. Figure 17-3 shows this solution.
Sometimes, a class that will become an Observer may already exist. In this case, I may not want to modify it. If so, I can easily adapt it with the Adapter pattern. Figure 17-4 shows an example of this.
The Observable Class: A Note to Java developers.The Observer pattern is so useful that Java contains an implementation of it in its packages. The Observable class and the Observer interface make up the pattern. The Observable class plays the role of the Subject in the Gang of Four's description of the pattern. Instead of the methods attach, detach, and notify, Java uses addObserver, deleteObserver, and notifyObservers, respectively (Java also uses update). Java also gives you a few more methods to make life easier.[1] |
[1] See http://java.sun.com/j2se/1.3/docs/api/index.html for information on the Java API for Observer and Observable.