There are several great object systems for Perl, and Moose is one of them. But Moose comes with a compile-time penalty that smaller applications may not be willing to pay, particularly for certain CGI or command-line scripts. Moose is incredibly feature-rich, and you may not need all of those features all the time.
Mouse to the rescue! Mouse is a lightweight object system with a subset of Moose's features. The goal throughout its development has been to make it syntactically consistent with Moose so if you later need to switch to the heavier-duty Moose, you can just substitute 'Moose' for 'Mouse' everywhere in your codebase, and things should just work.
Let's take a quick look at Mouse's capabilities. For this discussion, here's a code snippet defining three classes:
package Vehicle;
use Mouse;
has 'engine_type' => ( is => 'rw', isa => 'Str' );
has 'num_of_wheels' => ( is => 'rw', isa => 'Int' );
has 'max_passengers' => ( is => 'rw', isa => 'Int' );
has 'color' => ( is => 'rw', isa => 'Str' );
sub repaint {
my ($self, $new_color) = @_;
$self->color($new_color);
}
__PACKAGE__->meta->make_immutable();
package Bicycle;
use Mouse;
extends 'Vehicle';
has '+engine_type' => ( default => 'human' );
has '+num_of_wheels' => ( default => '2' );
has '+max_passengers' => ( default => '1' );
has 'basket_capacity' => ( is => 'rw', isa => 'Int', default => '0' );
before 'color' => sub {
my $self = shift;
$self->basket_capacity(0); # Take off the basket when we repaint
};
__PACKAGE__->meta->make_immutable();
package GoodsWagon;
use Mouse;
extends 'Vehicle';
has 'max_load_capacity' => ( is => 'rw', isa => 'Int' );
__PACKAGE__->meta->make_immutable();
Some basics
Regular Perl mongers will note that I did not use strict; use warnings;
in this code, something that is almost always encouraged. Mouse imports those for you, so you don't have to.
Also, see the call to meta->make_immutable()
at the bottom of each of the three packages? When you make a class immutable, you're telling Mouse (or Moose) that you're not going to be adding any more attributes, methods, or roles to the class. Doing this will speed things up at runtime, with a small cost when the class is first being loaded. It's recommended for most classes, so I included it here.
Attributes
Each of the three packages has a group of attributes, defined using the has
keyword. Because all of them are 'rw'
in the is
attribute parameter, Mouse will automatically create a reader and a writer for the attribute, visible to users of the object:
use Bicycle;
my $bike = Bicycle->new( color => 'Purple' );
print $bike->engine_type; # human
$bike->basket_capacity(4); # install a basket, with capacity 4
$bike->repaint('Blue'); # Bicycle inherits this sub from Vehicle.
# We could also just $bike->color('Blue');
In the Vehicle class, I'm defining some characteristics common to many types of vehicles, but not setting values for any of them. You could stop there, but in the application, we want to have some special sorts of vehicles with additional characteristics.
In the Bicycle class, we set defaults for engine_type
, num_of_wheels
, and max_passengers
that are rational for a bicycle, and we also set a default for basket_capacity
that indicates no basket is installed by default. Meanwhile, in GoodsWagon, we define the vehicle's load capacity, something of value to know about large commercial vehicles. In both of the subordinate classes, we inherit the attribute characteristics of the parent class, using the extends
keyword.
There are a lot of things you can do here, like making an attribute required, define "builder" subroutines or methods to construct or calculate attributes, trigger other actions when an attribute is set, and much more.
Subroutine inheritance
As you can see in the example above, classes that extend
others inherit their subroutines. You can override the subroutine by writing it into the child class, but in many cases, there may be an easier way: before
, after
, and around
. These subroutines are executed before the subroutine or attribute named in the case of before
, afterward in the case of after
, and both before and after in the case of around
. I've included a before
in the Bicycle class, which removes the instance's basket capacity before you change the color (you wouldn't want to repaint your bicycle with the basket still mounted, now, would you?). Cleverly, you could also use an after
for other tasks you might want to do, such as lubricating the chain.
Roles
Finally, let's look at roles. Roles are class definitions in Mouse that describe something another class may do or have as a common characteristic. Here's one, which I'm snagging and enhancing from the Moose documentation:
package Breakable;
use Mouse::Role;
has 'is_broken' => ( is => 'rw', isa => 'Bool' );
sub break {
my $self=shift;
$self->is_broken(1);
}
sub fix {
my $self=shift;
$self->is_broken(0);
}
We've added an attribute here that could be used across Vehicle and all of its child types, and (since it's pretty generic) for other types of machines or other objects completely unrelated to vehicles. If we add with 'Breakable';
to the Vehicle class above, then we can do something like this:
use GoodsWagon;
my $wagon = GoodsWagon->new( color => 'Red', max_load_capacity => '4000' );
print $wagon->is_broken ? 'Broken down!' : 'Running fine!'; # Running fine!
$wagon->break;
print $wagon->is_broken ? 'Broken down!' : 'Running fine!'; # Broken down!
$wagon->fix;
Roles are useful when you want to have a behavior that is common across more than one tree of classes. If Vehicles were the only things that you wanted to make breakable in this way, then you'd just put that functionality in the Vehicle class.
You have a lot of choices for object management systems in Perl. Choosing Mouse for small projects can speed them up over running Moose, and if the project grows, you can easily switch to Moose for its much larger feature set. It's easy to get up to speed quickly with Mouse; check out the Mouse documentation for more details.
Comments are closed.