Home > Articles > Programming > Ruby

  • Print
  • + Share This
This chapter is from the book

This chapter is from the book

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.

  • + Share This
  • 🔖 Save To Your Account