Can anyone make an argument why I would ever want to use inheritance when I have roles available aside from modules which require you to inherit from them (e.g., DBIx::Class)? The more I use roles, the less I understand why anyone would want to use the inheritance model. Even with our admittedly complex requirements on our BBC team, inheritance just doesn't seem necessary.
Currently I'm rewriting a huge subsystem of "builders" where every single object uses the PIPs::API::Builder::Role::DoesBuilder role. Traditionally I would have made that a base class, but by making it a role, I gain the composition-time safety that inheritance can't give me. Counter-arguments welcome.
Don't be fooled, over use of roles is just as bad as over use of MI.
I worry that perhaps your a little too blinded by the shiney right now. Now, it seems the BBC system your working on was full of MI, so in this case, yes it probably makes a lot of sense to go heavily with the roles. And DBIx::Class is also uses MI very heavily and the planned Mooseification of it involves converting much of that to roles. But there are some things that fit really nicely into the inheritance model and those things really should use inheritance and not be shoe-horned into roles. The more common case that I have found is that roles and inheritance are best when used together (right tool for the right job basically).
Here is a simple scenario in which I think a combination of roles and inheritance make the most sense. Basically it is using roles to build the lower levels of your system and then inheritance in the higher levels.
When building a larger system it is nice to be able to use roles for the lower level parts of the system to share behaviors. By behaviors I mean cross cutting behaviors that really do not make sense with inheritance. An example might be a HasDBICSchema role which provides access to a DBIx::Class schema object. Your web controller might need access to the schema as well as your maint scripts, to make these two different parts of your system share the a similar base class would probably not make much sense.
Now, once you have your lower level components built, it is nice to then be able to use subclassing with these components for your specific tasks/needs. I say this because by the time you have these components all built your classes are probably fairly large and complex. Working with large roles and their flattened namespaces can get pretty hairy when you get to this level of your system design. You end up needing to do a lot of exclusion and aliasing, which in my opinion, is a code smell.
In many cases, a simple subclass that overrides maybe one or two methods to add extra functionality is so much simpler and more maintainable then having to play composition games with multiple roles all the time. This is especially true if this is a living system where to do things with roles might require refactoring of the original code (we can't predict all possible needs and use cases). Sure, this might seem a little hackish to some, but elegance has to be balanced with risk when you have a live system running (and perhaps a limited budget).
Basically I think it is best to ask yourself "Is this a true is-a relationship?". If the answer is "yes", then use inheritance. If your honest with yourself and think things through you should end up with a nice shallow inheritance hierarchy and a decent sized collection of behavioral roles.
- Stevan
Re:Roles are not a silver bullet
Ovid on 2009-05-27T21:24:16
Thanks. I think that gives some great perspective on this. You had previously mentioned that overuse of roles was bad and I never asked you to follow up on your comment. I'll reread that paper (I read it years ago before I really was familiar with traits) and hopefully I'll have a better perspective now.
One of the papers that the Traits guys wrote called Applying Traits to the Smalltalk Collection Hierarchy is very relevant to this. It shows what is, in my opinion, a perfect example of where roles are best used and how they are superior to inheritance. The key thing to note is that they still retained a lot of inheritance where there was a true is-a relation, but they removed a lot of unnecessary inheritance that was really only there to achieve code reuse.
- Stevan
Re:My rule of thumb...
xsawyerx on 2009-05-28T08:01:11
That's what I do as well, it just makes the most sense to me.
Jonathan and Stevan's advice to think "is a dog an animal, does it walk?" is commonly sensible and easy to follow, but when I code, I don't always think that way. It's easier for me to look ahead and say "am I going to create an instance of this thing? Do I want to be able to?". It helped me refactor some things to roles and I haven't looked back on those. Of course, some people naturally think of the verb.
It's nice to see how there's good advice on how to simply figure how whether to use MI or roles, a question that plagues many excellent developers.
Re:My rule of thumb...
Ovid on 2009-05-28T08:19:48
To be fair, advice on what to do is great, but advice on why to do it is more valuable. Stevan's input was great because he was offering concrete reasons. If those reasons are pertinent to your project, than they're worth taking into account. Otherwise, I'm still unsure that holding on to inheritance is particularly worthwhile. Most of the arguments in favor of it ultimately seem to revolve around syntactic issues (as does the role debate that I've had with chromatic and others).
Re:My rule of thumb...
xsawyerx on 2009-05-28T09:00:12
That's a good point (that why is more valuable than what) but to me it was too general. Actually, a lot of why is too general to me. I don't have the programming depth perception some other really good programmers might have (Stevan, yourself, I could keep naming
:) and so a rule of thumb can be useful for me to judge this situation on my own. I wouldn't say it never failed me but I'll say it's been relatively good to me. As for a situation I might want MI? How about...
# this is probably a stupid example, but...
package Feline;
use Moose;
has 'agile' => (
is => 'ro',
isa => 'Bool',
default => 1,
);
package Feline::Cat;
use Moose;
extends 'Feline';
has 'name' => (
is => 'ro',
isa => 'Str',
);
package main;
my $cat = Feline::Cat->new(
name => 'Legacy', # yes, that's my cat's real name
agile => 0, # she's a lazy kitteh
);Re:My rule of thumb...
Aristotle on 2009-05-30T18:36:40
I think you need both parts. The How without the Why is, as you say, mechanical and and shallow, and prone to dogma; on the other hand, the Why without the How is too unconstrained and prone to lose touch with reality. I think Whys are not very useful in a vacuum; they must serve as justification for particular Hows in order to be worthwhile, much like Hows without a justifying Why are rarely useful (though not never – sometimes there is a meta-Why, namely, consensus).
I've found that a reasonable rule of thumb can be, if you can say "X is a Y" and it sounds sensible in relation to the real world entities, then inheritance is probably right. On the other hand, if it "X does Y" sounds much more natural then it's time to use a role. Of course, you can twist words to stick it into either, but I think we can probably agree that "Dog does Walk" sounds much natural than "Dog is Walker" and that "Dog is Animal" is more natural than "Dog does Animal".