do we need that
Posted by jcarroll
Sep 05
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
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#.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
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:
1 2 3 4 5 6 |
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.
Comments on this post
Sep 05
Jon Yurek said,
It’s defined in RAILS_ROOT/vendor/rails/activerecord/lib/active_record/associations.rb, line 555, right where the documentation says it’s defined.
Anyway, while, yes, you’d see the whole implementation of a class in the same file in Java, I’ve seen classes that were multiple-thousand lines long, simply because they had to implement everything their interfaces said they did. Good references and good separation leading to small code beats out explicitness leading to huge code, IMO.
Really, as much as people say that Ruby is self-documenting, I’ve found that the developers actually taking the time to write out how things are supposed to work leads to a much better understanding of how their code operates and how you’re supposed to be using it.
Sep 05
Jon Yurek said,
Also, what’s up with the
stuff in your DomainObject model?def self.included(clazz) clazz.class_eval do def save end def update end def destroy end end endSep 05
Mike Tunnicliffe said,
I’m not sure I understand your point regarding losing polymorphism in your Java example that supports multiple inheritance>
public static void main (String args[]) { User user = new User(); Company company = new Company();
Is there some limitation inherent in “mixing in” these classes? I mean, they are superclasses, right? Is there some reason you can’t treat these subclasses as if they were one of their ancestors? I mean, in normal Java inheritance you can.
Politely confused, Mike
Sep 05
jare care said,
mike good call that was a poor example.
Sep 05
Jeremy Weiskotten said,
An alternative to inheritance (class or interface) and modules/mixins is composition. You can add behavior to a class by introducing a dependency on another class—a “has a” relationship. It can be (in Ruby especially) a heavyweight approach, but there can be benefits.
The distinction between mixing in a module and using composition is subtle, and I don’t think I have enough Ruby experience yet to be able to analyze and appreciate all the ins and outs. Any thoughts?
Sep 06
jare care said,
Jeremy,
I would consider mixing in a module in a class in Ruby a form of multiple inheritance and therefore it would be an example of adding behavior to a class via inheritance.
In the example with a user having many addresses, the code that was mixed in via a module included a getter, #addresses, which would return a collection of addresses. An argument could be made that since a user delegates all its address management, adding/removing to/from its addresses, to another object, a collection, specifically an Array, that that’s a form of composition.
Sep 12
Jason Watkins said,
One thing you don’t need is all that def self.included stuff. Maybe I’m missing something, but you only need that if you want macro methods you can use in subclass definitions.
should do.
Sorry, comments are closed for this article.
© 2000 - 2009 by thoughtbot, inc.
written by a bushel of tiny robots
Come “ride the toad” on Hoptoad, the app error app.
Thunder Thimble: Brand monitoring for social media.
Widgetfinger: Simple content management for simple websites.
Tee-Bot, funny shirts your friends won't understand!
Umbrella Today: “It’s like totally the simplest weather report ever, Julie.”
Thoughtbot
thoughtbot is a technology consulting firm that provides web application development and design services. We focus on building modern systems, embracing good ideas and delivering elegant solutions.
Interested in learning Rails?
Sign up for our beginning or advanced training.
Archives