do we need that

Jared Carroll

In Ruby there’s 2 ways to add behavior to a class:

  1. inheritance
  2. modules

Now inheritance is the same in Ruby as just about everywhere else.

Modules bring something different to the table that’s not as common in lanugages. Modules are a form of multiple inheritance. A couple languages that provide support for classes and objects do offer multiple inheritance e.g. C++ and lisp’s CLOS.

Let’s take a look at C++.

We’ll take the example of both users and companys having addresses i.e. being addressable.

class DomainObject {

  public:
    bool save ();
    bool update ();
    int destroy ();

}

class Address : public DomainObject {

  public:
    string to_string () {
      return this.street + this.city + this.state;
    }

  private:
    string street;
    string city;
    string state;
}

class Addressable {

  public:
    vector <Address> get_addresses () {
      return this.addresses;
    }

  private:
    vector <Address> addresses;  // a vector is a dynamically expanding array
}

class User : public DomainObject, public Addressable {
}

class Company : public DomainObject, public Addressable {
}

void print_addresses (Addressable addressable) {
  for (vector<Address>::iterator itr = addressable.get_addresses().begin();
       itr != addressable.get_addresses().end();
       itr++) {
    Address address = (Address) *itr
    cout << address->to_string() << endl;
  }
}

int main () {

  Addressable * user = new User;
  Addressable * company = new Company;

  print_addresses(user);
  print_addresses(company);

  // i miss this
  delete user;
  delete company;

  return 0;
}

OK bear with me here.

In the above C++ we see that our User and Company classes both inherit from DomainObject and Addressable (multiple inheritance). An Addressable has many Address s. By inheriting from Addressable, User and Company get that behavior for free (inheriting from DomainObject gives us basic CRUD for free).

Now C++ is a statically typed language i.e. we have to declare the types of all our variables. In the main function I create 2 references, 1 to a new User object and 1 to a new Company object. However we refer to them through the Addressable interface. And via polymorphism, #print_addresses is nice and extendable, as long as you pass it something that inherits from Addressable it works.

Now let’s look at this example in Java/C#.

public class DomainObject {
  public boolean save () {}
  public boolean update () {}
  public boolean destroy () {}
}

public class Address extends DomainObject {

  public String getAddress () {
    return this.street + this.city + this.state;
  }

  private String street;
  private String city;
  private String state;
}

public interface Addressable {
  public List getAddresses ();
}

public class User extends DomainObject implements Addressable {
  public List getAddresses () {
    return this.addresses;
  }

  private List addresses;
}

public class Company extends DomainObject implements Addressable {
  public List getAddresses () {
    return this.addresses;
  }

  private List addresses;
}

public class App {

  public static void main (String args[]) {
    Addressable user = new User();
    Addressable company = new Company();

    printAddresses(user);
    printAddresses(company);
  }

  public static void printAddresses (Addressable addressable) {
    for (Iterator itr = addressable.getAddresses().iterator();
         itr.hasNext();) {
      Address address = (Address) itr.next();
      System.out.println(address.toString());
  }
}

Now Java does NOT support multiple inhertance, only single inheritance. Java did introduce a first-class construct called an interface that can only contain a specification of beahvior and not any implementation of it. And a class in Java can implement any number of interfaces. Unfortunately, this results in us duplicating the logic of the Addressable interface in both the User and Company classes. However, the #printAddresses function is still nice and extendable like in the C++ example, pass in an Addressable and it works.

Let’s look at this in Ruby.

module DomainObject

  def self.included(clazz)
     clazz.class_eval do
       def save
       end
       def update
       end
       def destroy
       end
    end
  end

end

module Addressable

  def self.included(clazz)
    clazz.class_eval do
      attr_reader :addresses
    end
  end

end

class Address
  attr_reader :street, :city, :state

  def to_s
    street + city + street
  end
end

class User
  include DomainObject
  include Addressable
end

class Company
  include DomainObject
  include Addressable
end

def print_addresses(addressable)
  addressable.addresses.each {|each| puts each}
end

Here we mix in both DomainObject and Addressable into User and Company. Now all #print_addresses cares about is that the object passed in understands #addresses and that the object returned from that message understands #each. Since types no longer matter combined with the ability to simulate multiple inheritance using modules, inheritance is entirely unnecessary in Ruby. You can gain the benefits of multiple inheritance in C++ (shared behavior who’s implementation is in 1 place i.e. DRY) and still write very generic code like #print_addresses.

In Rails there’s no reason the following command:

ruby script/generate model user

Can’t generate the following code instead:

class User
  include ActiveRecord::Base
end

Provided of course ActiveRecord::Base is now a module.

Java’s lack of multiple inheritance but support for interfaces results in the most explicit code because if I look at a class I can see all its behavior right there, no looking in other files (provided I always used interfaces in Java and never extends). Whereas in C++ and Ruby, when looking at a class you can’t see all its behavior because it may be mixed in by a class or module; you’ll see that a class is using a mixin but you’ll still have to find its file to see what it mixes in. This can be a lot of fun, trying finding where ActiveRecord::Base#has_many is defined in Rails.

Now there is a general backlash against multiple inhertiance usually citing the naming ambiguities that can occur if you inherit from 2 classes that in turn inherit from the same class and the complexity it adds to the language implementation. However, I do not have years of C++ experience using multiple inheritance and this mixin style of programming, so I’m not yet sure on how this effects maintainability.