Have you ever struggled with how to structure your class hierarchy, given a common type of functionality which applies to some classes but not others? Say for instance you have a class named
Animal. With this
Animal class you can derive all of your animal classes. Take for instance a chicken, for which you subclass
Animal to a class you call
One of the features you’d like to implement is that an animal makes a noise. Chickens, for instance, cluck. Some animals, however, don’t make a noise. Take a seahorse for instance, it doesn’t make noise. It is every bit an animal, but it doesn’t make noise. There are many other mute animals across many different families of animals, so it doesn’t really seem to fit in to your hierarchy. For this purpose, you can use a Role in Moose. A role allows you to dynamically add functionality to an existing Moose class (as many of them as you like) without having to mangle your nice, clean class hierarchy. Think of it as a grab-bag of functionality for your class which can also be used by other classes regardless of their parentage.
Here, have some code
[gist id=”6417632″ file=”Animal.pm”]
And here’s your
[gist id=”6417632″ file=”Chicken.pm”]
Here’s the Role we create for noisy animals:
[gist id=”6417632″ file=”Noisy.pm”]
And here’s the program to demo the whole thing:
[gist id=”6417632″ file=”animal_prog.pl”]
Here’s why you might care
Animal, you see I give it only a
name attribute. Surely there’s a bunch of other stuff that you could attribute to animals than that, I’ll let you expand attributes as you see fit 🙂
Chicken, I already know what the animal name is so I override the name attribute to have a default of “chicken”. For a moment, skip past the
after portions, we’ll come back to those.
Noisy class, notice that I
use Moose::Role, that right there makes this a role. Now, in the
Noisy class I make one method,
make_noise(). This method starts the phrase of what the animal says, and gives us a hook on which we can add functionality. Since the chicken makes a specific noise, we’ll want to use that hook to say exactly what the animal says.
Now, go back to the
Chicken class for a moment. The with statement loads the
Noisy module and then binds in the role using all of that
Moosey magic. Then, the after statement binds to the
make_noise() method of the
Noisy role and causes this anonymous function to be executed immediately following the code defined by the role. In this specific case that means that
make_noise() defined in
"The chicken says: " and then the
Chicken class’ binding to the
make_noise() method says
Now look at the
animal_prog.pl program. What’s really cool about roles is that you can tell whether or not an instance uses a specific role using the
does() method. This method takes a role’s class name and returns true or false depending on whether the specified role is used or not.
Here’s the output of the program:
The seahorse doesn't really say much.
The chicken says: bok bok.
Moose is full of neat little things like this which help to reduce boiler-plate code. Reducing this repetitive code helps to reduce the amount of code you have to write and it also helps to reduce bugs.
I hope you found this write-up useful. I have been enjoying posting these.