Factoring Out the Observable Support
Implementing the Observer pattern in Ruby is usually no more complex than our Employee example suggested: just an array to hold the observers and a couple of methods to manage the array, plus a method to notify everyone when something changes. But surely we can do better than to repeat this code every time we need to make an object observable. We could use inheritance. By factoring out the code that manages the observers, we end up with a functional little base class:
class Subject def initialize @observers=[] end def add_observer(observer) @observers << observer end def delete_observer(observer) @observers.delete(observer) end def notify_observers @observers.each do |observer| observer.update(self) end end end
Now we can make Employee a subclass of Subject:
class Employee < Subject attr_reader :name, :address attr_reader :salary def initialize( name, title, salary) super() @name = name @title = title @salary = salary end def salary=(new_salary) @salary = new_salary notify_observers end end
This is not a completely unreasonable solution; in fact, Java has gone exactly this route with its java.util.Observable class. But, as we saw in Chapter 1, using inheritance can cause grief. The problem with using Subject as a base class is that it shuts out the possibility of having anything else as a base class. Ruby allows each class to have exactly one superclass: Use up your single superclass of Employee on Subject and you are done. If our domain model demands that we make Employee a subclass of OrganizationalUnit or DatabaseObject, we are out of luck; we cannot also make it a subclass of Subject.
The problem is that sometimes we want to share code between otherwise unrelated classes. Our Employee class wants to be a Subject, but perhaps so does that spreadsheet cell. So how can we share the Subject implementation without using up our one allotted superclass?
The solution to this dilemma is to use a module. Recall that a module is a package of methods and constants that we can share among classes, but that does not soak up the single allotted superclass. If we recast our Subject class as a module, it does not really look all that different:
module Subject def initialize @observers=[] end def add_observer(observer) @observers << observer end def delete_observer(observer) @observers.delete(observer) end def notify_observers @observers.each do |observer| observer.update(self) end end end
Using the new Subject module is simplicity itself. We include the module and call notify_observers when something changes:
class Employee include Subject attr_reader :name, :address attr_reader :salary def initialize( name, title, salary) super() @name = name @title = title @salary = salary end def salary=(new_salary) @salary = new_salary notify_observers end end
By including the Subject module, our new Employee class gains all of the Subject methods: It is now fully equipped to play the subject in the Observer pattern. Note that we needed to call super() in the initialize method of Employee, which has the effect of calling initialize in the Subject module.3
Building our own Subject module was great fun and a good exercise in creating a mixin module. But would you really want to use the Subject that we just cooked up? Probably not. The Ruby standard library comes with a fine, prebuilt Observable module that provides all of the support you need to make your object, well, observable. Using it is not all that different from using the Subject module that we built:
require 'observer' class Employee include Observable attr_reader :name, :address attr_reader :salary def initialize( name, title, salary) @name = name @title = title @salary = salary end def salary=(new_salary) @salary = new_salary changed notify_observers(self) end end
The standard Observable module does feature one twist that we omitted from our hand-built version. To cut down on redundant notifications to the observers, the standard Observable module requires that you call the changed method before you call notify_observers. The changed method sets a Boolean flag that says the object really has changed; the notify_observers method will not actually make any notifications if the changed flag is not set to true. Each call to notify_observers sets the changed flag back to false.