[ Perl tips index ]
[ Subscribe to Perl tips ]
Moose is a complete object system for Perl 5. It provides a simple and consistent syntax for attribute declaration, object construction and inheritance, without the need to understand how those things are implemented. Furthermore, Moose allows you to separate the use of your data from its underlying representation, thus you need not know whether Moose is using a hash, array or something else in which to store your data. This allows the programmer to focus on ``what'' the code is doing, rather than ``how''.
Using Moose gives us more easy accessors, clever classes, roles, types and coercions and loads of other cool stuff. However the basic OO rules still apply.
To create a class in Perl we create the package and use the Moose
module:
package PlayingCard;
use Moose;
# we now have a Moose-based class
1;
That's it! Moose also turns on strict and warnings for us, so we
don't have to do that ourselves, although nothing bad happens if we
turn them on explicitly.
Classes in Moose can be used straight away, as it establishes a constructor for us automatically. Admittedly our class isn't very useful yet, but we can still create simple objects if desired:
use PlayingCard;
my $card = PlayingCard->new(); # works!
If you need your constructor to do more than just create an object with the
given attributes then Moose provides some handy construction hooks:
BUILDARGS and BUILD. To read more about these read
Moose::Manual::Construction.
Just as in regular OO Perl, we add subroutines to our class in order to define object methods:
package PlayingCard;
use Moose;
sub show_card {
my $self = shift;
....
}
1;
We then call them on our object, just as we did before:
use PlayingCard;
my $card = PlayingCard->new(); # works!
# Print out the card's value
print $card->show_card();
One of the huge improvements that Moose provides is an easy and straight-forward way to handle accessors. We declare these with an English-like syntax and Moose handles everything else.
# Create our class (this also creates our constructor)
package PlayingCard;
use Moose;
# Add the suit attribute
has 'suit' => (
is => 'ro', # Once card is created, suit won't change
isa => 'Str', # Suits will be strings
required => 1, # This attribute must be defined
);
# Add the value attribute
has 'value' => (
is => 'ro', # Once card is created, value won't change
isa => 'Str', # Values will be strings
required => 1, # This attribute must be defined
);
# Returns the card name
sub show_card {
my $self = shift;
return $self->value() . " of " . $self->suit();
}
__PACKAGE__->meta->make_immutable; # explained below
no Moose; # turn off Moose-specific scaffolding
1;
Once we have created these, we now have our full PlayingCard object. We can make a whole bunch of cards, show their values, put them in a deck or whatever:
use PlayingCard;
my $card1 = PlayingCard->new(
value => "ace",
suit => "hearts",
);
my $card2 = PlayingCard->new(
value => "king",
suit => "spades",
);
print $card1->show_card();
print $card2->show_card();
# trying to change a value is not allowed
$card1->suit("diamonds"); # ERROR
When we declare an attribute we must specify whether whether
it is read-only (ro) or read-write (rw). Failing to do this
will not cause a warning, but will result in the attribute not being
usable which is a hard bug to track down. An attribute which is
read-only can only be set when the object is created, while attributes
which are read-write may have their value changed as required.
has 'suit' => (
# Suit is read-only
is => 'ro',
isa => 'Str',
);
In the case of our card, it doesn't make sense for a card to change either its suit or its value after creation time. However, in a game where cards can be face-up or face-down, it does make sense for these to change:
has 'visible' => (
# visible is read-write
is => 'rw',
isa => 'Bool',
);
Don't forget to specify whether your attribute is read-only or read-write.
Moose provides a number of basic attribute types which we can use for our
class attributes. These form a simple hierarchy, where any more
specialised type may be used in the place of a more generalised type (for
example you can use an integer (Int) instead of a number (Num) and either
can be used where a string is expected. You can see this hierarchy by
reading perldoc Moose::Util::TypeConstraints and it is also reproduced
below:
Any
Item
Bool
Maybe[`a]
Undef
Defined
Value
Str
Num
Int
ClassName
RoleName
Ref
ScalarRef
ArrayRef[`a]
HashRef[`a]
CodeRef
RegexpRef
GlobRef
FileHandle
Object
Any type followed by a type parameter ['a] can be parameterised. Thus
you can write:
ArrayRef[Int] # an array of integers HashRef[CodeRef] # a hash of str to CODE ref mappings Maybe[Str] # value may be a string, may be undefined
You can also create your own types and constraints, which we will cover later.
These are not a strong typing system for Perl 5. These are constraints to ensure that attributes for Moose-based objects contain what you expect them to.
Once you have declared your attributes in your class, you automatically receive
accessor functions for them. These are named by the attribute name and if
your attribute is marked as read-write (rw) then you can use them to
change the value as well:
my $card = PlayingCard->new(
value => "two",
suit => "clubs",
decoration => "flowers",
);
print $card->suit(); # prints "clubs";
$card->decoration("money"); # decoration is now money
You can tell Moose to name your accessors differently if you want:
has 'decoration' => (
is => 'rw',
isa => 'Str',
reader => 'get_decoration',
writer => 'set_decoration',
);
Then later we call these as required:
$card->set_decoration("money"); # decoration is now money
print $card->get_decoration(); # prints "money"
Moose will still check the type constraints and run any triggers with methods named like this.
At the end of our PlayingCard class, above, we had the following line:
__PACKAGE__->meta->make_immutable;
Calling make_immutable makes Moose faster as your class' object
construction and destruction becomes inlined and no longer invokes the
meta API. It also declares that you do not intend to make any further changes
to the class via the metaclass API (which is what gives us all the cool Moose
features). This is why we do this at the end of our class declaration. In
essence this means that we cannot modify our class during the run-time of the
calling program, which is a rare thing to want to do, and thus a small price to
pay for the speed increase.
For many objects, we would like to run code when an object is
created. This may be to attach to a resource, run validation checks,
or merely fill in some sensible defaults. Moose allows us to do
this by defining our own BUILDARGS and BUILD methods.
The BUILDARGS method, if defined, is executed before
the object is constructed. It's useful for tweaking our
arguments before they hit Moose. One very common use of
BUILDARGS is to support object construction with a non-hash
syntax. For example, we may allow our playing cards to be
constructed with a single argument (eg: 'Kh' for the king of hearts).
around BUILDARGS => sub {
my ($orig, $class, @args) = @_;
# $orig is Moose's (or our parent's) BUILDARGS
# subroutine. This allows us chain together
# classes that all do their own argument handling.
if ( @args == 1 and ! ref $args[0] ) {
# If we only have one argument, and it's not a
# reference, then extract our card information.
my ($value, $suit) = ($args[0] =~ /^(\w)(\w)$/);
return $class->$orig(
value => $value,
suit => $suit,
);
}
else {
return $class->$orig(@args);
}
};
The use of the around keyword will be explained more in another
tip.
The BUILD method is called immediately after an object is
constructed. It's often useful to perform extra validation
steps, connect to a database, open a file, or otherwise do work
that needs to occur at object construction. For example, in the
game of Pontoon, tens are removed from the deck.
package PlayingCard::Pontoon;
use Moose;
extends 'PlayingCard';
sub BUILD {
my ($self) = @_;
if ($self->value eq 'T') {
die "Tens are not allowed in Pontoon\n";
}
}
There is no need to call parental BUILD methods; in fact, it's
a grave mistake to do so. Moose ensures that all BUILD methods
are called. Parental BUILD methods are guaranteed to be called
before their children.
[ Perl tips index ]
[ Subscribe to Perl tips ]
This Perl tip and associated text is copyright Perl Training Australia. You may freely distribute this text so long as it is distributed in full with this Copyright noticed attached.
If you have any questions please don't hesitate to contact us:
| Email: | contact@perltraining.com.au |
| Phone: | 03 9354 6001 (Australia) |
| International: | +61 3 9354 6001 |
Copyright Perl Training Australia. Contact us at contact@perltraining.com.au