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.