Saturday, May 27, 2006

Mixins, Interfaces, and Multiple Inheritance

As I've been doing Ruby programming for the last little while, I've discovered the concept of Mixins. Now, my programming experience in OO languages other than Java has been limited. I only did about a year of C++ at my first job before I ended up switching to Java, so that's why I haven't really been exposed to the notion of a Mixin before. But now that I've been using it a bit, I think it is in fact a rather wonderful concept! Interestingly, I've cooked up my own erzatz mixins in Java in the past without fully realizing what I was doing, but I think proper mixins really are more elegant!

A module in ruby is similar to a class with one major exception: It cannot be instantiated. You can execute methods on it statically, so for example you can have a Math module with functions like sin, consin, absolute_value, and so on. More interestingly, you can mix in a module into a proper Ruby class, whereupon all of the module's methods become available to the class as normal member methods - and the module's methods can in fact interact with methods supplied by the class. It is similar to multiple inheritance as implemented in C++ for example, but slightly weaker. C++ has full multiple inheritance, so it can do both interfaces as implemented in Java and mixins in Ruby as special cases. To make a C++ 'interface', you create a class with all pure virtual methods. To create a 'mixin module', you create a class with some pure virtual methods and at least one fully-implemented method which calls the pure virtual methods. However, in C++, you can do even more. You can simply write any number of fully functional classes, and then create another class which extends all of them. In OO, this means that wherever you have a reference to any of those base classes, you can substitute the subclass. Thus, every method from all base classes is valid for the subclass in terms of class invariants as well as preconditions and postconditions. Both Java and Ruby designers chose not to provide that degree of generality. I am guessing they felt encouraging developers to inherit from more than one fully-functional class is asking for trouble, especially as the inheritance chain becomes more than just one level deep! Mixins are a nice compromise: You can inherit functionality from a variety of sources (better than Java), but you can't arbitraily exend any number of actual classes with all the attendant headaches.

In Java, I have used delegation to do the same kind of thing that mixins provide, but without fully realizing what I was doing, until now! A while back I needed to write some code to find a loop in a network of oil facilities. Here is roughly what I did in order not to pollute the Facility class (loop finding after all is a lower-level behaviour) and to make the code easy to write test-first.
public abstract class Node {
public abstract List sendsTo();
public List findLoop() {
//recursively goes through the nodes in sendsTo.
//If it finds itself as it's going along, it returns
//a list of nodes that form the loop. Otherwise it
//returns an empty list. In my implementation, if
//there are multiple loops, only the first one
//this method finds it returned.
}
}
To get this to work with my Facility class, I implemented a class
FacilityNode as follows:
public class FacilityNode extends Node {
public FacilityNode(Facility facility) {
this.facility = facility;
}
public List sendsTo() {
//return a list of all dispositions of oil from this
//facility with non-zero volumes. Ignore dispositions
//of gas or water, and ignore oil dispositions that have
//0 volume.
}
}
Then in the Facility class, I use this FacilityNode as follows:
public class Facility {
//... whole bunch of other methods

public List findLoop() {
return new FacilityNode(this).findLoop();
}
}
This is admittedly far more awkward than simply mixing Node into the Facility class and implementing sendsTo, but it's the same general idea. One can see that the Node class can be expanded to include a variety of operations related to the topologies of networks and thereby provide far better overall cohesion in the application. In order to take advantage of these mixed-in methods, one need only implement a much small number of required methods, like sendsTo for example! Mixins are cool!

1 comment:

code_martial said...

You could also use CRTP to implement mixins through templates, which basically lets the base class invoke non-virtual methods of the derived class within its own implementation and the entire function resolution happens at compile time.