Perl 5's default object system is flexible, but minimal. You can build great things on top of it, but it provides little assistance for some basic tasks. Moose is a complete object system for Perl 5See perldoc Moose::Manual
for more information.. It provides simpler defaults, and advanced features borrowed from languages such as Smalltalk, Common Lisp, and Perl 6. Moose code interoperates with the default object system and is currently the best way to write object oriented code in modern Perl 5.
A Moose object is a concrete instance of a class, which is a template describing data and behavior specific to the object. Classes use packages (packages) to provide namespaces:
This Cat
class appears to do nothing, but that's all Moose needs to make a class. Create objects (or instances) of the Cat
class with the syntax:
Just as an arrow dereferences a reference, an arrow calls a method on an object or class.
A method is a function associated with a class. Just as functions belong to namespaces, so do methods belong to classes, with two differences. First, a method always operates on an invocant. Calling new()
on Cat
effectively sends the Cat
class a message. The name of the class, Cat
, is new()
's invocant. When you call a method on an object, that object is the invocant:
Second, a method call always involves a dispatch strategy, where the object system selects the appropriate method. Given the simplicity of Cat
, the dispatch strategy is obvious, but much of the power of OO comes from this idea.
Inside a method, its first argument is the invocant. Idiomatic Perl 5 uses $self
as its name. Suppose a Cat
can meow()
:
Now all Cat
instances can wake you up in the morning because they haven't eaten yet:
Methods which access invocant data are instance methods, because they depend on the presence of an appropriate invocant to work correctly. Methods (such as meow()
) which do not access instance data are class methods. You may invoke class methods on classes and class and instance methods on instances, but you cannot invoke instance methods on classes.
Constructors, which create instances, are obviously class methods. Moose provides a default constructor for you.
Class methods are effectively namespaced global functions. Without access to instance data, they have few advantages over namespaced functions. Most OO code rightly uses instance methods, as they have access to instance data.
Every object in Perl 5 is unique. Objects can contain private data associated with each unique object--these are attributes, instance data, or object state. Define an attribute by declaring it as part of the class:
In English, that reads "Cat
objects have a name
attribute. It's read-only, and is a string."
Moose provides the has()
function, which declares an attribute. The first argument, 'name'
here, is the attribute's name. The is => 'ro'
pair of arguments declares that this attribute is r
ead o
nly, so you cannot modify it after you've set it. Finally, the isa => 'Str'
pair declares that the value of this attribute can only be a str
ing.
As a result of has
, Moose creates an accessor method named name()
and allows you to pass a name
parameter to Cat
's constructor:
Moose will complain if you pass something which isn't a string. Attributes do not need to have types. In that case, anything goes:
Specifying a type allows Moose to perform some data validations for you. Sometimes this strictness is invaluable.
If you mark an attribute as readable and writable (with is => rw
), Moose will create a mutator method which can change that attribute's value:
An ro
accessor used as a mutator will throw the exception Cannot assign a value to a read-only accessor at ...
.
Using ro
or rw
is a matter of design, convenience, and purity. Moose enforces no particular philosophy in this area. Some people suggest making all instance data ro
such that you must pass instance data into the constructor (immutability). In the Cat
example, age()
might still be an accessor, but the constructor could take the year of the cat's birth and calculate the age itself based on the current year. This approach consolidates validation code and ensure that all created objects have valid data.
Instance data begins to demonstrate the value of object orientation. An object contains related data and can perform behaviors with that data. A class describes that data and those behaviors.
Moose allows you to declare which attributes class instances possess (a cat has a name) as well as the attributes of those attributes (you cannot change a cat's name; you can only read it). Moose itself decides how to store those attributes. You can change that if you like, but allowing Moose to manage your storage encourages encapsulation: hiding the internal details of an object from external users of that object.
Consider a change to how Cat
s manage their ages. Instead of passing a value for an age to the constructor, pass in the year of the cat's birth and calculate the age as needed:
While the syntax for creating Cat
objects has changed, the syntax for using Cat
objects has not. Outside of Cat
, age()
behaves as it always has. How it works internally is a detail to the Cat
class.
Calculating ages has another advantage. A default attribute value will do the right thing when someone creates a new Cat
object without passing a birth year:
The default
keyword on an attribute takes a function referenceYou can use a simple value such as a number or string directly, but use a function reference for anything more complex. which returns the default value for that attribute when constructing a new object. If the code creating an object passes no constructor value for that attribute, the object gets the default value:
... and that kitten will have an age of 0
until next year.
Encapsulation is useful, but the real power of object orientation is much broader. A well-designed OO program can manage many types of data. When well-designed classes encapsulate specific details of objects into the appropriate places, something curious happens: the code often becomes less specific.
Moving the details of what the program knows about individual Cat
s (the attributes) and what the program knows that Cat
s can do (the methods) into the Cat
class means that code that deals with Cat
instances can happily ignore how Cat
does what it does.
Consider a function which displays details of an object:
It's obvious (in context) that this function works if you pass it a Cat
object. In fact, it will do the right thing for any object with the appropriate three accessors, no matter how that object provides those accessors and no matter what kind of object it is: Cat
, Caterpillar
, or Catbird
. The function is sufficiently generic that any object which respects this interface is a valid parameter.
This property of polymorphism means that you can substitute an object of one class for an object of another class if they provide the same external interface.
show_vital_stats()
cares that an invocant is valid only in that it supports three methods, name()
, age()
, and diet()
which take no arguments and each return something which can concatenate in a string context. You may have a hundred different classes in your code, none of which have any obvious relationships, but they will work with this method if they conform to this expected behavior.
Consider how you might enumerate a zoo's worth of animals without this polymorphic function. The benefit of genericity should be obvious. As well, any specific details about how to calculate the age of an ocelot or octopus can belong in the relevant class--where it matters most.
Of course, the mere existence of a method called name()
or age()
does not by itself imply the behavior of that object. A Dog
object may have an age()
which is an accessor such that you can discover $rodney
is 9 but $lucky
is 4. A Cheese
object may have an age()
method that lets you control how long to stow $cheddar
to sharpen it. age()
may be an accessor in one class but not in another:
Sometimes it's useful to know what an object does and what that means.
A role is a named collection of behavior and stateSee the Perl 6 design documents on roles at http://feather.perl6.nl/syn/S14.html and research on Smalltalk traits at http://scg.unibe.ch/research/traits for copious details.. While a class organizes behaviors and state into a template for objects, a role organizes a named collection of behaviors and state. You can instantiate a class, but not a role. A role is something a class does.
Given an Animal
which has an age and a Cheese
which can age, one difference may be that Animal
does the LivingBeing
role, while the Cheese
does the Storable
role:
Anything which does this role must supply the name()
, age()
, and diet()
methods. The Cat
class must explicitly mark that it does the role:
The with
line causes Moose to compose the LivingBeing
role into the Cat
class. Composition ensures all of the attributes and methods of the role part of the class. LivingBeing
requires any composing class to provide methods named name()
, age()
, and diet()
. Cat
satisfies these constraints. If LivingBeing
were composed into a class which did not provide those methods, Moose would throw an exception.
Now all Cat
instances will return a true value when queried if they provide the LivingBeing
role. Cheese
objects should not:
This design technique separates the capabilities of classes and objects from the implementation of those classes and objects. The birth year calculation behavior of the Cat
class could itself be a role:
Extracting this role from Cat
makes the useful behavior available to other classes. Now Cat
can compose both roles:
Notice how the age()
method of CalculateAge::From::BirthYear
satisfies the requirement of the LivingBeing
role. Notice also that any check that Cat
performs LivingBeing
returns a true value. Extracting age()
into a role has only changed the details of how Cat
calculates an age. It's still a LivingBeing
. Cat
can choose to implement its own age or get it from somewhere else. All that matters is that it provides an age()
which satisfies the LivingBeing
constraint.
Just as polymorphism means that you can treat multiple objects with the same behavior in the same way, this allomorphism means that an object may implement the same behavior in multiple ways.
Pervasive allomorphism can reduce the size of your classes and increase the code shared between them. It also allows you to name specific and discrete collections of behaviors--very useful for testing for capabilities instead of implementations.
To compare roles to other design techniques such as mixins, multiple inheritance, and monkeypatching, see http://www.modernperlbooks.com/mt/2009/04/the-why-of-perl-roles.html.
When you compose a role into a class, the class and its instances will return a true value when you call DOES()
on them:
Perl 5's object system supports inheritance, which establishes a relationship between two classes such that one specializes the other. The child class behaves the same way as its parent--it has the same number and types of attributes and can use the same methods. It may have additional data and behavior, but you may substitute any instance of a child where code expects its parent. In one sense, a subclass provides the role implied by the existence of its parent class.
Consider a LightSource
class which provides two public attributes (enabled
and candle_power
) and two methods (light
and extinguish
):
(Note that enabled
's writer
option creates a private accessor usable within the class to set the value.)
A subclass of LightSource
could define a super candle which provides a hundred times the amount of light:
extends
takes a list of class names to use as parents of the current class. If that were the only line in this class, SuperCandle
objects would behave the same as LightSource
objects. It would have both the candle_power
and enabled
attributes as well as the light()
and extinguish()
methods.
The +
at the start of an attribute name (such as candle_power
) indicates that the current class does something special with that attribute. Here the super candle overrides the default value of the light source, so any new SuperCandle
created has a light value of 100 candles.
When you invoke light()
or extinguish()
on a SuperCandle
object, Perl will look in the SuperCandle
class for the method, then in each parent. In this case, those methods are in the LightSource
class.
Attribute inheritance works similarly (see perldoc Class::MOP
).
Method dispatch order (or method resolution order or MRO) is obvious for single-parent classes. Look in the object's class, then its parent, and so on until you find the method or run out of parents. Classes which inherit from multiple parents (multiple inheritance)--Hovercraft
extends both Boat
and Car
--require trickier dispatch. Reasoning about multiple inheritance is complex. Avoid multiple inheritance when possible.
Perl 5 uses a depth-first method resolution strategy. It searches the class of the first named parent and all of that parent's parents recursively before searching the classes of subsequent parents. The mro
pragma (pragmas) provides alternate strategies, including the C3 MRO strategy which searches a given class's immediate parents before searching any of their parents.
See perldoc mro
for more details.
As with attributes, subclasses may override methods. Imagine a light that you cannot extinguish:
Calling extinguish()
on a glowstick does nothing, even though LightSource
's method does something. Method dispatch will find the subclass's method. You may not have meant to do this. When you do, use Moose's override
to express your intention clearly.
Within an overridden method, Moose's super()
allows you to call the overridden method:
This subclass adds a warning when trying to light or extinguish a light source that already has the current state. The super()
function dispatches to the nearest parent's implementation of the current method, per the normal Perl 5 method resolution order.
Perl's isa()
method returns true if its invocant is or extends a named class. That invocant may be the name of a class or an instance of an object:
Moose provides many features beyond Perl 5's default OO. While you can build everything you get with Moose yourself (blessed_references), or cobble it together with a series of CPAN distributions, Moose is worth using. It is a coherent whole, with good documentation. Many important projects use it successfully. Its development community is mature and attentive.
Moose takes care of constructors, destructors, accessors, and encapsulation. You must do the work of declaring what you want, but what you get back is safe and easy to use. Moose objects can extend and work with objects from the vanilla Perl 5 system.
Moose also allows metaprogramming--manipulating your objects through Moose itself. If you've ever wondered which methods are available on a class or an object or which attributes an object supports, this information is available:
You can even see which classes extend a given class:
See perldoc Class::MOP::Class
for more information about metaclass operations and perldoc Class::MOP
for Moose metaprogramming information.
Moose and its meta-object protocol (or MOP) offers the possibility of a better syntax for declaring and working with classes and objects in Perl 5. This is valid Perl 5 code:
The MooseX::Declare
CPAN distribution uses Devel::Declare
to add new Moose-specific syntax. The class
, role
, and method
keywords reduce the amount of boilerplate necessary to write good object oriented code in Perl 5. Note specifically the declarative nature of this example, as well as the lack of my $self = shift;
in age()
.
While Moose is not a part of the Perl 5 core, its popularity ensures that it's available on many OS distributions. Perl 5 distributions such as Strawberry Perl and ActivePerl also include it. Even though Moose is a CPAN module and not a core library, its cleanliness and simplicity make it essential to modern Perl programming.
Hey! The above document had some coding errors, which are explained below:
- Around line 3:
-
A non-empty Z<>
- Around line 7:
-
Deleting unknown formatting code N<>
- Around line 352:
-
Deleting unknown formatting code N<>
- Around line 459:
-
A non-empty Z<>
- Around line 462:
-
Deleting unknown formatting code N<>
Deleting unknown formatting code U<>
Deleting unknown formatting code U<>
- Around line 603:
-
Deleting unknown formatting code U<>
- Around line 624:
-
A non-empty Z<>