Roles, h'uh, what are they good for?

What is a role? Put simply, roles are a form of code reuse. Often, the term shared behavior is used. Roles are said to be consumed and the methods ( including attribute accessors ) are flattened into the consuming class.

One of the major benefits of roles is they attempt to solve the diamond problem encountered in multi-inheritance by requiring developers to resolve name collisions manually that arise in multi-inheritance. Don't be fooled however, roles are a form of multi-inheritance.

I often see roles being used in ways they shouldn’t be. Let’s look at the mis-use of roles, then see an example of shared behavior.

I’m using that word inheritance a lot for a reason, one of the two ways I see roles most often misused is to hide an inheritance nightmare.

"Look ma, no multi-inheritance support, no problem. I’ll just throw stuff in roles and glum them on wherever I really want to use inheritance. It all sounds fancy, but I am just lumping stuff into a class cause I don’t really understand OO principals."

One tenet of good object oriented design (GOOD) and not just OOPS ( object oriented programming syntax ) is Composition over Inheritance. All too often roles get used where composing an object, possibly with method forwarding, or an external function, is really what you want. How do I know? Because there is no use of the wrappee self in the ‘role’. Roles are a form of delegation ( this term is mis-used for method forwarding in some documentation ). Delegation is analogis to inheritance, and in delegation the *self* in the wrappee role is the consuming class’ (wrapper) self. Once execution is handed to a role method, all work is done inside role sub routines till either a side effect finishes or a result is generated then execution returns to the code in the wrapper class. Yet, in the class consuming the role, “self” is never called or not called in a way that is meaningful to the class.

This brings me to the next mis-use; roles for code organization. If there is a one to one relationship between the role and it’s use, then this is nothing more than code organization (and obfuscation), stuffing code under the bed like an adolescent boy who told to clean his room, doesn't make it clean. Roles being used as a way to break a 5000 line file into five 1000 line files. Why would someone do this? Because in their heart they know that they have that class doing way too much - it smells. Not understanding good OO, the class grew into a god object. Knowing this is wrong but either not knowing why or just not caring, code was broken into roles along some perceived associations. As other developers came along, prior art was followed and possible encouraged. Unfortunately, as functionality was further glummed on, things just got thrown into the most convenient role or the use of roles exploded as a way of expanding class functionality but pretending like it was being done in a good OO way. There is no shared behavior, the roles are consumed in exactly one class. How to get out of this is a topic for another article. For now, the code from the role should be merged into the same file as the class so you can really see what you have and go from there.

Another version of the one to one is when roles are used to define an interface, either with some function bodies or not, but mostly just “requires ”. In the outset it seemed like a good idea, but now a decade later the only place any interface defining role is used is in the one class that implements those methods. This is noise and liability in your code, delete the roles and remove the "with" line in the class.

So what are roles good for? In Perl roles are analogies to traits. They can define behavior with full methods built out and or require the composing class implement methods to parameterize the behavior.

So what does shared behavior look like? Here's a contrived example. Please don't do this, there are better ways to handle this sort of thing, I'm just showing shared behavior, not the correct way to handle THIS problem.


package Comparable;
use Role::Tiny;

requires 'area';
requires 'perimeter';

sub can_contain {
my ($self, $other) = @_;
my $can_contain = $self->area >= $other->area;
return $can_contain;
}

sub is_bigger {
my ($self, $other) = @_;
my $is_bigger = $self->perimeter > $other->perimeter;
}


package Square;
use Role::Tiny::With;
with 'Comparable';

sub area {}
sub perimeter {}


package Circle;
use Role::Tiny::With;
with 'Comparable';

sub area {}
sub perimeter {}


... somewhere else...
$square->can_contain($circle);
$circle->is_bigger_than($square);

Do you have any great examples of roles being used correctly?

As I researched this article, I discovered I don't think roles really fit with my view of objects in general. That is, they are often over used in ways that would be better suited by dependency injection, or external functions.

Peace.

2 Comments

In Mojolicious we recommend extending core classes with roles such that more than one extension can be used at once, notably when done to release to CPAN. You can see this especially in extensions to Test::Mojo. We had gotten to the point of several subclasses of Test::Mojo being on CPAN and it was realized that that meant that you could only use one. Once we started recommending roles (and indeed building some role functionality into Mojo core) that now you can choose to apply any of several CPANed Test::Mojo::Role:: role to your test application! It has made for a very nice ecosystem.

Leave a comment

About Jesse Shy

user-pic Perl pays for my travel addiction.