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 Chicken
.
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
Here’s your Animal
class:
And here’s your Chicken
class:
Here’s the Role we create for noisy animals:
And here’s the program to demo the whole thing:
Here’s why you might care
With the 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 🙂
For 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 with
and after
portions, we’ll come back to those.
For the 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 Moose
y 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 Noisy
outputs "The chicken says: "
and then the Chicken
class’ binding to the make_noise()
method says "bok bokn"
.
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:
seahorse
chicken
The seahorse doesn't really say much.
The chicken says: bok bok.
In Closing
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.
Just a note, in your Chicken class, you do not need to repeat the ‘is => rw’ and the ‘isa => Str’ options in your attribute, the ‘+name’ form will do that for you already.
– Stevan
Stevan, thank you for your correction! It’s not too often that the author comes to correct me, I really appreciate it.