Adding aspects with Ruby mixins

2008-08-28 by mira

Java has a single inheritance model. A class can therefore only extend from one base class.

This keeps the object model simple - and simple is good. As a consequence, this inheritance relationship is more precious than other associations between classes.

There can be only one

A common design guideline is to favor delegation over inheritance, that way a model can evolve more freely.

Now, let’s consider an implementation of the well known Observer Pattern. If I have a domain model and want to turn the domain objects into observable objects, I basically have to provide methods for registration and de-registration of observers/listeners, keep track of the registrations and fire events upon change of the domain objects state. I have several options for the design:

  1. Implement the observable logic in a common base class and make all domain classes inherit from it
  2. Implement the observable logic in a support class. All domain classes implement the IObservable interface and simply delegate to the support class
  3. Use some AOP framework to do the black magic

Option 1. keeps the domain objects code clean but wastes the precious inheritance for infrastructure purposes.

Option 2. uses friendly delegation but clutters the domain classes code.

Option 3. leaves no traces in the source code but is harder to read and maintain as the observable aspect of the domain objects is cut out of the source completely and is only represented in a declarative way.

Overall, I favor Option 2 but am not satisfied with it. Instead of solving the problem in Java, I started to wonder how this could be solved in Ruby. After some research on the net, I knew that mixins can do the trick.

mira@moe:~/wdir/ruby$ cat mixin.rb

module Observable
def fire_property_change(source, property, old, new)
  if ! @listeners.nil?
    @listeners.each do |l|
      l.property_changed(source, property, old, new)
    end
  end
end

def add_property_change_listener(l)
  @listeners = ( @listeners || [] )
  @listeners << l
end
end

class Person
include Observable

attr_accessor :nick

def nick=(nick)
  old = @nick
  @nick = nick
  fire_property_change(self, :nick, old, nick)
end
end

class PropertyChangeListener

def property_changed(source, property, old, new)
  puts "property_changed: #{source.class}.#{property.to_s}  #{source.to_s}"
  puts "  old: #{old}"
  puts "  new: #{new}"
end
end

p = Person.new
p.add_property_change_listener(PropertyChangeListener.new)

p.nick = "Joe"
p.nick = "Jim"

mira@moe:~/wdir/ruby$ ruby mixin.rb
property_changed: Person.nick  #<Person:0xb7c67b10>
  old:
  new: Joe
property_changed: Person.nick  #<Person:0xb7c67b10>
  old: Joe
  new: Jim

Archive

architecture