Anda di halaman 1dari 47

Moose

OOP for Perl.


What we expect from OOP

● Classes (methods, types, attributes, open recursion...)

● Encapsulation

● Aggregation

● Message passing and dynamic dispatch

● Inheritance and polymorphism

● Abstraction

Skip definitions
Classes
● Templates for defining objects state and
behavior
Encapsulation

● Specifying which classes may use the


members of an object e.g:

public, protected or private, determining


whether they are available to all classes,
sub-classes or only the defining class.
Aggregation

● Object containing another object


Message passing and dynamic dispatch

● Message passing is the process by which an object sends data to


another object or asks the other object to invoke a method

● when a method is invoked on an object, the object itself determines


what code gets executed by looking up the method at run time in a
table associated with the object (needed when multiple classes
contain different implementations of the same method )
Inheritance and polymorphism
● allows the programmer to treat derived class members just like
their parent class's members ("Subclasses" are more specialized
versions of a class, which inherit attributes and behaviors from
their parent classes, and can introduce their own.)

● a class has all the state and behavior of another class -


polymorphism
What OOP features offers Perl?
● MOP in Perl5:
● Class - package

● Object - blessed reference (a scalar type holding a reference to object


data – object may be a hash, array, or reference to subroutine.)

● Method - a subroutine within a package

● Constructor – a method that returns reference to blessed variable


What OOP features offers Perl?

package Car;
sub new {
my $class = shift;
my $brand = shift;
my $self = {};
$self->{'brand'} = $brand;
bless ($self, $class);
return $self;
}
What OOP features offers Perl?
● Encapsulation – in general only by convention.
● Some tricks: private variable, private method, private function

package Mom; package Kido;

sub new{ use base qw(Mom);


my $class = shift;
my $self = { sub ask_for_pin{
'CARD_PIN' => '0000', my $self = shift;
}; my $how = shift;
return &{$self}($how);
my $closure = sub { }
my $arg = shift;
if ($arg and $arg eq 'nice'){
return $self->{'CARD_PIN'}; package main;
} my $mom = Mom->new();
else { my $kido = Kido->new();
return 'forget!';
} print $kido->ask_for_pin('not nice');
}; print $kido->ask_for_pin('nice');
bless($closure, $class);
return $closure;
};


“Inside-out” classes not handy... “There is only one way to do it...” .
What OOP features offers Perl?
{
{ package Mom;
package Mom; (...)
(...)
sub _prepare_pastry{
my $_prepare_pastry = sub{ if (caller ne __PACKAGE__){
print "makin' pastry 2 \n"; die;
}; }
else {
sub make_cookies{ print "makin' pastry \n";
&{$_pastry}; }
print "cookies ready\n"; }
}
}
sub make_cookies{
_prepare_pastry();
package main;
print "cookies ready\n";
(...)
}
package main;
}
my $mom = Mom->new();
package main;
$mom->make_cookies();
(...)
my $mom = Mom->new();
$mom->_prepare_pastry(); #this
compiles!
$mom->make_cookies();
What OOP features offers Perl?
● Aggregation

package Person; my $eva = Person->new();


my $car1 = Car->new();
sub new { $car1->brand( 'Fiat' );
my $class = shift;
my $self = {}; $eva->cars->[0]->brand(); #returns 'Fiat'
$self->{'cars'} = ();
bless( $self, $class );
return $self;
}

sub cars {
my $self = shift;
my $car = shift;
if ( ref( $car ) eq 'Car' ) {
push @{ $self->{'cars'} }, $car;
}
return $self->cars;
}
What OOP features offers Perl?
● Inheritance (multi-object) and polymorphism: use base()

package Person; package Employee;

sub new { use base qw(Person);


my $class = shift;
my $self = {}; sub new {
my $class = shift;
bless( $self, $class ); my $job = shift;
return $self; my $self = $class->SUPER::new();
} $self->{'job'} = $job;

sub name { bless( $self, $class );


my $self = shift; return $self;
if ( @_ ) { $self->{'name'} = shift } }
return $self->{'name'};
} #overriding
sub say_name {
sub say_name { my $self = shift;
my $self = shift; $self->SUPER::say_name();
printf( "My name is %s. \n", $self->{'name'} ); printf( "My job is %s.\n", $self->{'job'} );
} }

#inheritance and polymorphism


my $adam = Employee->new( 'Developer' );
$adam->name( 'Adam' );
$adam->say_name
What OOP features offers Perl?
● Abstraction – only objects
package File; my $bmp = BMP->new();
use Carp; $bmp->load();

sub new { my $jpg = JPG->new();


my $class = shift; $jpg->load();
my $self = {};
bless ($self, $class);
return $self;
}

sub load{
croak('not implemented');
}

package BMP;
use base qw(File);

package JPG;
use base qw(File);

sub load{
my $self = shift;
print "File loaded. \n";
}
What OOP features offers Perl?

What is missing?
● The in-language support for OO features is minimal.
● No type checking, no horizontal inheritance, no encapsulation...

There is more than one way to do it!


Moose

There is more than one way to do it, but sometimes


consistency is not a bad thing either!
Moose is Perl.

Moose =
Class::MOP + semantic
Moose is Perl.

● common system for building classes


● new levels of code reuse, allowing you to improve your
code with features that would otherwise be too complex or
expensive to implement
Moose is Perl.
Everything missing in OO Perl you can find in Moose + some
important bonuses!

● Type matching
● Coercion - the ability to convert types of one type to another when
necessary
● Encapsulation
● Method Modifiers - the ability to add code that runs before, after or
around an existing method
● Horizontal inheritance - roles - the ability to add predefined
functionality to classes without sub-classing

… and much more.


Moose – first example.
Sets strict and warnings
package Vehicle;
use Moose;
MOOSE CLASS

has 'RegNum' => ( Class attributes.


'is' => 'rw',
'isa' => 'Str');

sub goto{
my $self = shift; Class methods.
my $place_name = shift;
print "Going to $place_name \n";
}

__PACKAGE__->meta->make_immutable;

no Moose;
1;

use Vehicle; We get new() for free


my $avehicle = Vehicle->new();

$acar->RegNum('123456');
print $acar->RegNum;
We get also default
$acar->goto('Oriente');
accessors for free.
Moose – first example.
12 lines of code from previous slide give us all this:
package Vehicle;
use strict;

sub new {
my $class = shift;
my %args = @_;

my $self = { '_RegNum' => undef };

if ( exists( $args{'RegNum'} ) && defined( $args{'RegNum'} ) ) {


$self->{'_RegNum'} = $args{'RegNum'};
}

return bless($self, $class);


} package Vehicle;
use Moose;
sub RegNum {
my $self = shift; has 'RegNum' => (
my $regnum = shift; 'is' => 'rw',
if ( $regnum ) { 'isa' => 'Str');
$self->{'_RegNum'} = $regnum;
} sub goto{
return $self->{'_RegNum'}; my $self = shift;
} my $place_name = shift;
print "Going to $place_name \n";
sub goto{ }
my $self = shift;
my $place_name = shift; no Moose;
print "Going to $place_name \n"; 1;
}

1;
Lets have a closer look...
● Arguments

(...) 'rw' or 'ro' – tells which accessors will be created

has 'RegNum' => (


'is' => 'rw',
'isa' => 'Str'
);
Moose types:
(...)
Bool, Str, Num, Int, ArrayRef, HashRef,
and more

'is' – defines if argument is read-write or read-only


'isa' – defines type of the attribute (and validate it during assignment)
Lets have a closer look...
● Read- only arguments

has 'RegNum' => ( If we restrict access to the attribute...


'is' => 'ro', the default accessor won't be created.
'isa' => 'Str'
);

This method doesn't exist


$avehicle->RegNum('123456'); – it results in compilation error

The only right moment to set up the


my $avehicle = Vehicle->new( attribute is during initialization.

'RegNum'=>'1234'

);
We can still read it wherever we want.
print $avehicle->RegNum."\n";
What more we can do with attributes?
● Required arguments
Required attribute must be set.
It means that...
has 'RegNum' => (
'is' => 'ro',
'isa' => 'Str',
'required' => 1,
);
… this will not compile

#$acar = Vehicule->new();

my $avehicle = Vehicle->new('RegNum'=>'123456'); ...but we still can have illegal vehicle..


RegNum can be set to empty string.
my $avehicle = Vehicle->new('RegNum'=>'');

has 'RegNum' => (


'is' => 'ro',
'required' => 1,
);
BUT
my $avehicle = Vehicle->new('RegNum'=>undef);
If attribute has no type it can be
set to undefined
What more we can do with attributes?
●Clearer and predicate
Imagine... the vehicle is a broken and is waiting to be scraped, but first you must unregister it .

has 'RegNum' => (


'is' => 'ro', You can't use accessors because it is “ro”.
'isa' => 'Str',
'required'=> 1,
'clearer' => 'unregister',
but... you can set clearer to clean the value
'predicate'=>'is_registered',
);

and predicate to check if the


value exists
my $avehicle = Vehicule->new('RegNum'=>'1234');

print $avehicle->RegNum;

$avehicle->unregister();

if (!$avehicle->is_registered()){ The method you get for free.


print "Vehicle illegal!.\n";
}
What more we can do with attributes?
● Subtypes
Not every string can be the registration number...

package Vehicle; use TypeConstraints


use Moose; and create subtype
use Moose::Util::TypeConstraints;

subtype 'RegistrationNo'
=> as 'Str', parent type
=> where { /^(\d\d)(-)(\w\w)(-)(\d\d)$/ },
=> message {"$_ is not valid registration number."};

has 'RegNum' => ( compare $_ to something


'is' => 'ro',
'isa' => 'RegistrationNo',
'required'=> 1,
'clearer' => 'unregister', and use it
'predicate'=>'is_registered',
);

my $avehicle = Vehicle->new('RegNum'=>'11-xx-22'); Subtypes can be more complex


more examples here
#my $avehicle = Vehicle->new('RegNum'=>'111-xx-22');
One Step Further - MooseX.
● Custom types
Lets define where is our vehicle.... a 2D coordinate - two points: x and y that
must have positive value.

package MyTypes;
what we want to create
use Moose;
use MooseX::Types -declare => [qw(Coordinate Point)];
use MooseX::Types::Moose qw(Int HashRef);
what we use to create

subtype Point,
as Int, a point
where { $_>= 0},
message {'Must be positive number.'};

A coordinate that is a has of two valid points


subtype Coordinate,
as HashRef,
where { Point->check( $_->{'X'} ) and Point->check( $_->{'Y'} )},
message {'Incorect coordinate.'.$_ };

This is very useful type. We can pack it in separate package and reuse later.
One Step Further - MooseX.
●Custom types
Lets use the coordinate to define where is the vehicle.

use MyTypes qw( Coordinate );


import custom type

(...)
has 'place' => ( modify the vehicle class
'is' => 'rw',
'isa' => Coordinate,
(Vehicle.pm)
);
(...)

my $place = {'X'=>1, 'Y'=>3 };

$avehicle->place( $place );

use it where ever you need


One Step Further - MooseX.
● Custom types and coercion
In the end, in class definition,
But maybe it would be easier to use it this way...? Don't forget to turn on the
coercion on the argument
$avehicle->place( [4,5] );

package MyTypes;
use Moose;
use MooseX::Types -declare => [qw(Coordinate Point)];
use MooseX::Types::Moose qw(Int HashRef ArrayRef);
subtype Point,
as Int,
(...)
where { $_> 0}, has 'place' => (
message {'Must be positive number.'}; 'is' => 'rw',
'isa' => Coordinate,
'coerce' => 1,
subtype Coordinate,
);
as HashRef,
where { Point->check( $_->{'X'} ) and Point->check( $_->{'Y'} )},
message {'Incorect coordinate.'.$_ }; (...)

coerce Coordinate,
from ArrayRef,
via {
{'X'=>$_->[0], 'Y'=>$_->[1]}
};
We define how we can
transform one type to another.
1;
Private attributes – only convention.
The vehicle may change its place only when it moves ?

(...) This is what the manual says


#an attribute to be publicly readable, but only privately settable. – but this is only CONVENTION.
has '_place' => (
'is' => 'ro',
'isa' => Coordinate,
'coerce' => 1,
'writer' => '_set_place',
); reader and writer redefine the
names of custom accessors
sub goto{
my $self = shift;
my $coordinate = shift;
$self->_set_place($coordinate);
} Now we can “move” the vehicle.

(...)

$avehicle->goto([7,8]);
print Dumper($avehicle);
print 'The vehicle is at'.$avehicle->_place->{'X'}.','.$avehicle->_place-
>{'Y'}.'.';

#my $place = {'X'=>1, 'Y'=>3 }; However this still would work.


#my $other_place = [4,5]; MOOSE is PERL
#$avehicle->_set_place( $place );
Method modifiers
Mileage – every time the vehicle moves its mileage increase.

has '_mileage' => ( One more


'is' => 'ro',
'isa' => 'Num',
private attribute.
'required' => 1,
'default' => 0,
'writer' => '_set_mileage',
); Method modifier

Has access to attributes,


around 'goto' => sub { before they are modified by
my $orig = shift;
my $self = shift; method
my $coordinate1 = $self->_place;
my $x1 = $coordinate1->{'X'};
my $y1 = $coordinate1->{'Y'};
Executing main method
#@_ stores new coordinate
$self->$orig(@_);

my $coordinate2 = $self->_place;
my $x2 = $coordinate2->{'X'};
Accessing modified
my $y2 = $coordinate2->{'Y'}; attributes
my $distance = sqrt(($x1-$x2)**2+($y1-$y2)**2);

$self->_set_mileage($self->_mileage + $distance); other actions


};
Method modifiers
Mileage – every time the vehicle moves its mileage increase.

Define default value


has '_place' => (
'is' => 'ro',
'isa' => Coordinate,
'coerce' => 1,
'writer' => '_set_place',
'default' => sub {[0,0]},
);

print $avehicle->_mileage;
Every time we move vehicle
$avehicle->goto([7,8]);
$avehicle->goto([1,1]); the mileage increase.
$avehicle->goto([4,5]);

print $avehicle->_mileage
Lazy, lazy_build and trigger.
Imagine we want to sell the vehicle. The price depends on the mileage.

Lazy - value is calculated only when


has 'price' => (
'is' => 'ro', it is accessed and is unset.
'isa' => 'Num',
'predicate'=>'has_price',
'lazy_build'=>1,
'builder'=>'calculate_price', lazy + clearer = lazy_build
'init_arg'=>undef,
);

sub calculate_price { Lazy needs default value or builder


my $self = shift;
my $price = 100000;
if ($self->_mileage != 0){
$price = $price / $self->_mileage;
}
return $price; Method building the value.
}

$avehicle->goto([1,1]);
...despite we move, the price didn't change.
print $avehicle->price;
Lazy build calculates the value only if it is unset.
$avehicle->goto([4,5]); Therefore we need to clean it every time
we want to get updated value.
print $avehicle->price;
Lazy, lazy_build and trigger.
Let's run the clearer automatically.

has '_mileage' => (


Every time the mileage changes the
'is' => 'ro', clearer for price attribute is triggered.
'isa' => 'Num',
'required' => 1,
'default' => 0,
'writer' => '_set_mileage',
'trigger' => sub {
my $self=shift;
After this change the previous example
$self->clear_price; shows updated value every time we access it
}
);

Lazy build is very useful for attributes are expensive to calculate –


we calculate it only while reading.
sub sell_vehicle {
my $self = shift;
$self->clear_price;
return $self->price; We can also use method instead the trigger.
}
The price is updated every time we read it
$avehicle->goto([1,1]); using the method.
print $avehicle->price;
Useful for expensive calculation.
$avehicle->goto([4,5]);
print $avehicle->price;

print "The final price: ".$avehicle->sell_vehicle. "\n";


Code reuse - inheritance in Moose.
Is the vehicle car or bike?

package Car;
use Moose;
Parent class
extends 'Vehicle';

has 'fuel' =>(


'is' => 'rw',
'isa' => 'Int',
'default'=>0, New attribute
);

sub refuel {
my $self = shift;
$self->fuel($self->fuel + 10); New method
}

override goto => sub{


my $self = shift;
Overwritten method
if($self->fuel eq 0){
print 'No fuel';
return;
}
return super;
};
Calling super

no Moose
Code reuse - inheritance in Moose.

package Bike;
use Moose;

extends 'Vehicle';

has '+RegNum' => (


'required'=> 0,
); changing inherited attribute
no Moose;
1;

my $abike = Bike->new(); registration number is not linger required


Better code reuse – inheritance and roles.
Short explanation:

● Role is a “state or behavior that can be shared between classes.”

● Role is not a class – can't be subclassed, can't be extended … we can't create an object of a role .

● But we can use a role in a class, and methods of the role in an object.
Better code reuse – inheritance and roles.
We can create a 'truck' role and use it in our vehicle.
The only difference

package Truck; sub do_load {


use Moose::Role; my $self = shift;
my $load = shift;
has 'capacity' => (
'is' => 'ro', if(($self->capacity >= $load) and !$self->load){
'isa' => 'Int', $self->load(1);
'default'=>10, }else{
'required'=>1, print 'Load unsuccessful,';
); }
}

has 'load' => ( sub do_unload {


'is' => 'rw', my $self = shift;
'isa' => 'Bool', my $load = shift;
'default'=>0,
); if($self->load){
$self->load(0);
}else{
print 'Nothing to unload,';
}
}
We create a piece of functionality no Moose;
1;
exactly the same way as we were
creating a moose class.
Better code reuse – inheritance and roles.
We want to have a car with a “truck functionality” - a lorry.
package Lorry;
use Moose;

extends 'Car';
with 'Truck';
Lorry = Car with Truck role

no Moose;
1;

How to use Lorry? Lorry is a vehicle – mast have registration number.


(…)

use Lorry;

(…)
Lorry has a Truck role – mast have capacity.

my $alorry = Lorry->new('RegNum'=>'11-xx-23', 'capacity'=>50);


#$alorry->do_load(100); - more than capacity

$alorry->do_load(40);
#$alorry->do_load(40); #unsecessfull - is loaded
We can use all truck functions.
$alorry->do_unload();
$alorry->do_load(30);

#$alorry->goto([2,4]); - still can't go without the fuel


But Lorry is still a car
so won't go without fuel.
Better code reuse – inheritance and roles.
In the end we want to assure that the truck role will be only used with anything that uses fuel
(Have you ever seen a lorry-bike?)

package Truck;
use Moose::Role;
We require that a method 'fuel'
requires 'fuel'; is present in a class where we
use the Truck role.
(…)

use Moose;

extends 'Bike';
with 'Truck';

no Moose; Since bike doesn't use fuel


this code will not compile.

'Truck' requires the method 'fuel' to be implemented by 'Lorrybike' at


/usr/local/lib/perl5/site_perl/5.10.0/i386-linux-thread-multi/Moose/Meta/Role/Application.pm line 51
Moose::Meta::Role::Application::apply('Moose::Meta::Role::Application::ToClass=HASH(0xa5faa2c)',
'Moose::Meta::Role=HASH(0xa5f6a6c)', 'Moose::Meta::Class=HASH(0xa5f8754)') called at
/usr/local/lib/perl5/site_perl/5.10.0/i386-linux-thread-multi/Moose/Meta/Role/Application/ToClass.pm
line 31
(...)
BUILDARGS and BUILD
Let's go back to Vehicle. It would be awesome to create a vehicle this way :

$acar = Vehicule->new('11-xx-11');

BUILDARGS method is called as a class method


around BUILDARGS => sub {
before an object is created. It has direct access
my $orig = shift; to arguments passed in method call.
my $class = shift;

my %a = @_;
my %args = ();
if ( @_ == 1 ) {
$args{ 'RegNum' } = $_[0];
return $class->$orig( %args );
}
return $class->$orig( %a );
};
BUILDARGS and BUILD

There is a possibility to have a bike without registration. But we want to be warn about it...

BUILD method is called after an object is created.


sub BUILD {
my $self = shift;
if(!$self->RegNum){
if($self->isa('Bike')){
print "Vehicle without registration number.\n";
}
}
}
We will be warn about creating a
bike without a registration number.

my $abike = Bike->new();
my $abike2 = Bike->new('22-yy-66');
Aggregation and Native Traits
Let's create a garage for our vehicles.

package Garage; Notice: there is no


use Moose;
use Moose::Util::TypeConstraints; 'is' =>'ro|rw'

has 'vehicles' => (


'isa'=> 'ArrayRef[ Vehicle ]',
'default' => sub {[]},
'traits' => ['Array'],
'handles'=> {
Trait – a role added to attribute.
'add_vehicle' => 'push', Here we make the vehicles attribute
'get_vehicle' => 'shift', work as an array.
},
);

no Moose;
1;
Defining accessors.
(these accessors use type checking).
More native traits:
Array: push, pop, shift, unshit, map, sort, grep …
Hash: get, set, delete, exists, defined, clear, is_empty …
String: append, substr, length...
Bool: set, unset, toggle...
Code: execute, execute_method...
Aggregation and Native Traits
Let's park some vehicles...

my $garage = Garage->new();

$garage->add_vehicle($avehicle);
my $v = $garage->get_vehicle();

$garage->add_vehicle($acar);
my $acar2 = $garage->get_vehicle();

$acar2 and $acar is the same


(reference to the same object)
What more you can do in Moose?

For example:

● Delegation - "proxy" methods call some other method on an


attribute.

● Custom traits - MooseX

● Custom OO system – sub-classing methaclasses

● And much more...


Some tips
● If you don't know what is wrong try to load package from command line :

perl -Ilib/ -e 'use Coordinate'

● $object->dump for debugging


More information:

● http://www.oscon.com/oscon2010/public/schedule/detail/13673
Moose is Perl: A Guide to the New Revolution

http://www.slideshare.net/dtreder/moose-527243

Awesome presentation with huge dose of humor, but also very helpful.

http://search.cpan.org/~drolsky/Moose-1.21/

Moose::Manual and Moose::Cookbook

Anda mungkin juga menyukai