Drop the Superclass

I’ve worked with Java to Ruby converts for the past year and the #1 thing that comes up over and over is the excessive use of inheritance. I can understand why, I was taught Object Oriented Programming in C++. We spent a week on encapsulation (that was the end of “basic” programming) and en entire semester on inheritance and templating (that was the “advanced” class). Inheritance was driven into so many of our heads that we have a hard time not using it. We were told that this is where the power of OOP lies - this is how you get the most code reuse.

Unfortunately it’s just not true. The use of inheritance results in very strong coupling of your code. Any class is dependent on all of its superclasses. You can’t isolate this class without taking all of the superclasses with it. You can’t isolate the classes’ siblings without doing the same. You either have to make several copies of these classes (don’t do that…) or you have to move the entire chunk of code from project to project as a single unit. The latter is the only practical choice you have - but what if you just want to use a few pieces of functionality found in this code? Tough luck - it’s all or nothing. This leads to bloated code, poor code re-use, and hard to use APIs. Ironically this is what most of us are used to - so it seems normal to us when we come across it.

Since publishing is a large part of the industry I work in, there are often “publishing” methods that need to be added to our models. Every implementation I’ve seen so far injects a Publish class in between ActiveRecord and the model. This isn’t necessarily a bad thing on its own. With just one level of inheritance we can still achieve good code reuse. But where do go from here? If we want to add additional functionality at this stage we either need to add it all to Publish (even if it’s not in the publishing domain) or we need to add additional levels of inheritance. All of the sudden our code reuse has gone out the window. We’re going to have a hard time injecting this functionality into other code bases.

Using composite objects instead of inheritance can help in many cases. We can add the desired functionality into a separate class and then instantiate that class in the class we want to “extend.” Then you can call (or delegate to) these methods.

def Publish < ActiveRecord::Base belongs_to :article end

def Article < ActiveRecord::Base has_one :publish

def initialize @publish = Publish.new end

def before_save raise Publish::NotPublished unless @publish.is_published? end end

Wow this still sucks! We either have to use two tables in the database or initialize publish from data in the article table. Either way really sucks. We don’t want to require an additional table and relationship. We also don’t want data for the Publish model in the Article table. Composition will often work just fine in a class that isn’t doing any object-relation mapping - but in this case it causes more problems than it solves.

In the land of dynamic languages (like Ruby) we can get around this. Using a run-time patch that injects a decorator method into a class can provide a way to build up functionality on the fly. It can also be more descriptive. It’s not always clear what superclasses Publish is being built from. If you break all of this functionality out into modules that get mixed in via decorator methods - then it’s easy to see what the behavior of the class will be by just looking at the class definition.

This functionality is broken out into a Rails plugin that allows runtime changes to ActiveRecord (or ActiveResource). Here is what the (simplified) patch looks like:

module ActiveResource module Acts module Publishable def self.included(base) base.extend(ClassMethods) end

  module ClassMethods

    def acts_as_publishable(options={})    
      include ActiveResource::Acts::Publishable::InstanceMethods
    end

    def publish(id, options = {})
    end
  end

  module InstanceMethods
    def is_published?
      (status == "published" || status == "edited")
    end                
  end            
end

end end

ActiveResource::Base.send(:include, ActiveResource::Acts::Publishable)

Now here is what the model will look like:

class Article < ActiveResource::Base acts_as_publishable end

Now we can mix this functionality into any code base. If you have database fields that relate to publishing they can be mixed in via migrations when the plugin is installed.

Posted by chrisp Thu, 18 Sep 2008 22:38:00 GMT