Alternatives To Inheritance

This is repost of a use.perl entry I made. You can clearly see the "before" and "after". Which do you prefer?


I'm planning to write a long article about alternatives to inheritance. Part of the issues with multiple inheritance in Perl programs seems to stem, in part, because there doesn't seem to be enough written about this from the Perl perspective

There's also not enough perspective from a "large-scale" standpoint. I still recall one person defending "mixins" because in years of programming, he's only been bitten by the mixin ordering problem once. However, when you start working on large scale, mission-critical applications with teams of programmers being rotated on and off the project, issues with multiple inheritance, silently overriding methods, mixin ordering and other issues are much harder to notice, much less track down. And even comprehensive test suites can miss that subtle bug which depends on a very unusual combination of factors. I speak from very painful experience here.

One thing I'm toying with is the concept of "flat" hierarchies. For small application, hierarchies tend to become large, hard to understand, and often have multiple levels of hierarchy. Consider the following:

package My::ResultSet::Book;

use Moose;
extends 'My::ResultSet::Product';

sub search { ... }

Now what you don't see clearly is this:

My::ResultSet
         ^
         |
My::ResultSet::Product
         ^
         |
My::ResultSet::Book

Imagine that My::ResultSource::Product also has a search method which allows you do a full text search all all relevant data (author or artist, publisher, title, and so on), but the &My::ResultSource::Book::search method has a full text search of the contents of the book (something which presumable the My::ResultSource::CD class would not have). The programmer has to know about the contents of the base class(es) and know whether or not they're suitable to override and whether or not the override should call the SUPER method.

Now in such a trivial example, you might think "big deal". Fair enough. Now consider a non-trivial example (click the image for a larger version).

Just think about the cognitive load there. We have about 40 classes in that diagram [1]. If each implements an average of 10 menthods, you have about 400 methods you have to keep track of. When you add a new method (particularly "private" ones), it's easy to accidentally override a parent method. To be really safe, you need to search through all of the parent classes, but we know that often doesn't happen.

I think it's fair to say that for a "Book", being able to do some searching is important, but it's not inherently part of being a book. So let's do a quick rewrite of the above:

package My::ResultSet::Book;

use Moose;
extends 'My::ResultSet';

with qw(
My::Role::DoesProduct
My::Role::DoesSearch
My::Role::DoesFullTextSearch
);

# ...

Now that we potentially have relatively flat inheritance, we're less likely to worry about overriding parent methods. We also only have one abstract class to know about (assuming it delegates rather than inherits from its base class), and with the three roles provided, we can see at a glance what this class is capable of. In fact, we might even convert the "extends" to a role if we were zealous.

Of course, the role My::Role::DoesProduct might be poorly named as a Book isa product in the concept of an e-commerce solution, so perhaps it's important to understand what a product does. We might rename it My::Role::DoesPurchaseable or something similar.

I don't think this is appropriate in all cases, but it makes things very clear and easy to understand. And if we accidentally reimplement a method in one of those roles, we get a compile-time failure rather than a runtime failure which might or might not get exposed in tests (hint: Test::Class used properly can mitigate these problems).

--

1. Note that the "40 classes" is misleading. That's only a tiny portion of our code base. Just to give you perspective:

$ find lib/ -name '*.pm' |wc -l
     501

And that doesn't account for the CPAN modules we inherit from. That should give you an idea of the complexity. Also, this is much smaller than other code bases I've worked on.

Leave a comment

About Ovid

user-pic Have Perl; Will Travel. Freelance Perl/Testing/Agile consultant. Photo by http://www.circle23.com/. Warning: that site is not safe for work. The photographer is a good friend of mine, though, and it's appropriate to credit his work.