In Ruby there’s 2 ways to add behavior to a class:
- inheritance
- 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.