"I learned OO on Java, then graduated to C++. So, my first thought on reading the above was: roles here are analogous to Java interfaces — except that a role contains an implementation whereas an interface does not."
Bingo!
Or rather, roles may provide implementation. In the Roles::Jumper role, the role specifies that the name and jump methods are part of the interface, but only provides an implementation for jump; it requires classes that consume the role to provide name.
"But then I wondered: how is this different from good old (possibly multiple) inheritance?"
Conceptually it's similar, but roles offer some protection against some of the problems associated with multiple inheritance.
The classic one is when you inherit methods of the same name from multiple parents. With a good implementation of roles (and in this context, Role::Tiny, Moo::Role, Mouse::Role and Moose::Role are all "good") you'll get an exception raised if your role composition causes a method conflict.
The example in the Moose docs is that role Fragile and role Dancer might both have a break method. If you want to build a class FragileDancer that composes both of those roles, then the way to avoid raising that exception is for FragileDancer itself to provide a break method (which might call Fragile::break or Dancer::break or both), and thus resolve the conflict.
If Dancer and Fragile had been classes, and FragileDancer multi-inherited from both, then exactly what $fragiledancer->break did would be a bit of a mystery. It would depend on the order of the @ISA array. If you had some code that did:
$thing->break if $thing->isa("Fragile");
... then you might be surprised if $thing suddenly started break-dancing.
Roles force the FragileDancer class to resolve the conflict in a way that makes sense to that class. It forces the person writing FragileDancer to actually think about the break method; realise that it's a conflict, and realise that it's important to document what it does in FragileDancer because it might differ from the expectations of people familiar with Fragile and Dancer.
Roles can also be used as a kind of "tagging" for classes. For example, say you've got a bunch of classes which inherit from MyApp::Plugin. Maybe some of the plugins need to be activated at stage 1 of your app, and some need to be activated at stage 2. In that case, you can use empty roles to tag which stage each plugin needs to be activated at.
package MyApp::Plugin::Stage1 { use Role::Tiny }; package MyApp::Plugin::Stage2 { use Role::Tiny }; package MyApp::Plugin::Frobnicate { use Role::Tiny::With; with "MyApp::Plugin::Stage2"; } ...; foreach my $plugin ($self->plugins) { # The "DOES" method is like "isa", but checks roles # instead of checking inheritance. # if ($plugin->DOES("MyApp::Plugin::Stage2")) { $plugin->activate($self); } }
Some people have likened the difference between role composition and inheritance to horizontal versus vertical. If you have an inheritance hierarchy, roles are like pulling extra functionality in from the sides.
"In my present state of ignorance, the two techniques you illustrate look like syntactic variations on an identical theme, and I can’t see any reason to prefer one over the other."
In the simple pets example, it does make little difference whether you use roles or inheritance. However, in a more complex application where you want a class to be composed of functionality from several different packages, composing multiple roles generally has fewer gotchas than multiple inheritance does.
Update: I added some more stuff above about FragileDancer.
In reply to Re^3: OO: how to make a generic sub (use roles or inheritance)
by tobyink
in thread OO: how to make a generic sub
by Hossein
| For: | Use: | ||
| & | & | ||
| < | < | ||
| > | > | ||
| [ | [ | ||
| ] | ] |