A Better Way to Stay Informed
The trouble with this code is that it is hard-wired to inform the payroll department about salary changes. What do we do if we need to keep other objects—perhaps some accounting-related classes—informed about Fred's financial state? As the code stands right now, we must go back in and modify the Employee class. Needing to change the Employee class in this situation is very unfortunate because nothing in the Employee class is really changing. It is the other classes—the payroll and accounting classes—that are actually driving the changes to Employee. Our Employee class seems to be showing very little change resistance here.
Perhaps we should step back and try to solve this notification problem in a more general way. How can we separate out the thing that is changing—who gets the news about salary changes—from the real guts of the Employee object? What we seem to need is a list of objects that are interested in hearing about the latest news from the Employee object. We can set up an array for just that purpose in the initialize method:
def initialize( name, title, salary ) @name = name @title = title @salary = salary @observers = [] end
We also need some code to inform all of the observers that something has changed:
def salary=(new_salary) @salary = new_salary notify_observers end def notify_observers @observers.each do |observer| observer.update(self) end end
The key moving part of notify_observers is observer.update(self). This bit of code calls the update method on each observer,2 telling it that something—in this case, the salary—has changed on the Employee object.
The only job left is to write the methods that add and delete observers from the Employee object:
def add_observer(observer) @observers << observer end def delete_observer(observer) @observers.delete(observer) end
Now any object that is interested in hearing about changes in Fred's salary can simply register as an observer on Fred's Employee object:
fred = Employee.new('Fred', 'Crane Operator', 30000.0) payroll = Payroll.new fred.add_observer( payroll ) fred.salary=35000.0
By building this general mechanism, we have removed the implicit coupling between the Employee class and the Payroll object. Employee no longer cares which or how many other objects are interested in knowing about salary changes; it just forwards the news to any object that said that it was interested. In addition, instances of the Employee class will be happy with no observers, one, or several:
class TaxMan def update( changed_employee ) puts("Send #{changed_employee.name} a new tax bill!") end end tax_man = TaxMan.new fred.add_observer(tax_man)
Suppose we change Fred's salary again:
fred.salary=90000.0
Now both the payroll department and the tax man will hear about it:
Cut a new check for Fred! His salary is now 80000.0! Send Fred a new tax bill!
The GoF called this idea of building a clean interface between the source of the news that some object has changed and the consumers of that news the Observer pattern (Figure 5-1). The GoF called the class with the news the subject class. In our example, the subject is the Employee class. The observers are the objects that are interested in getting the news. In our employee example, we have two observers: Payroll and TaxMan. When an object is interested in being informed of the state of the subject, it registers as an observer on that subject.
Figure 5-1 The Observer pattern
It has always seemed to me that the Observer pattern is somewhat misnamed. While the observer object gets top billing—in fact, the only billing—it is actually the subject that does most of the work. It is the subject that is responsible for keeping track of the observers. It is also the subject that needs to inform the observers that a change has come down the pike. Put another way, it is much harder to publish and distribute a newspaper than to read one.