⬆️ ⬇️

Moose (X). Continuation

In continuation of the topic Use Moose; Modern OOP in Perl , I want to tell you about some interesting features that Moose and its extensions provide.

From the above-mentioned topic, you can understand that with Moose you do not have to spend time implementing the object model. But besides the automatic creation of accessors / mutators and the designer, there is also a lot of all sorts of usefulness and interest. And with the help of extensions, in general, Perl can be transformed beyond recognition (in a good, naturally, way).



I will give examples of the code from the module which I try to rewrite in style of modern perl.



Part 1. Moose





1.1 Changing inherited attributes


Let's start with the simple. As you could learn from the above-mentioned topic, the Moose-based classes can, in addition to methods, also inherit attributes. And they can also partially change their [inherited attributes]. Very comfortably. The situation where it may be necessary: ​​there is a base class and several inheriting from it. Among the attributes of the base class there are those that are necessary for all children, only the default value should be for everyone. You do not have to declare an attribute with all properties in each child (data type, access rights, etc.)

')

Code:

base class:

has request_type => ( is => 'ro', isa => 'Int', lazy => 1, default => undef, init_arg => undef, );



child:

has '+request_type' => ( default => 32, ); # default





1.2 Method Modifiers


In some ways they resemble decorators, although they are not. There are three modifiers: before, after and around. The first two are performed, respectively, BEFORE and AFTER the method to which they were applied. About the third is to tell a little more. The around modifier takes as an argument the name of the method to which it was applied, and already inside the modifier you yourself must explicitly call it [method]. It turns out what happens inside the modifier, and in the place where you need. Accordingly, you can do anything both BEFORE the call and AFTER .



Example of application: (the modifier encrypts a certain request after its creation, if the corresponding options are specified)

after create_request => sub {

my $self = shift;



return unless $self->cipher;



require Crypt::TripleDES;



my $ciphered_text = sprintf "Phone%s\n%s",

$self->trm_id,

unpack('H*', Crypt::TripleDES->new->encrypt3($self->request, $self->ciphering_key));



$self->request($ciphered_text)

};





1.3 augment + inner


This feature is what I especially like.

Imagine that there is a base class, which is a template of a certain abstract query in the same abstract service. There is a set of parameters that must be added to each request (for example, login, password, number, etc.). In order not to duplicate this addition in each class, we will move it to the base class, and all child classes will work only with a specific part of the query.



Immediately code:

base class:

method create_request() { #

my $req_node = inner() ;

$req_node->appendChild( $self->_create_extra_node($_, $self->$_) ) foreach qw(password serial);

$req_node->appendChild( $self->_create_simple_node('protocol-version', $self->protocol_version) );

$req_node->appendChild( $self->_create_simple_node('terminal-id', $self->trm_id) );

$req_node->appendChild( $self->_create_simple_node('request-type', $self->request_type) );



my $xml = XML::LibXML::Document->new('1.0', 'utf-8');

$xml->setDocumentElement($req_node);



$self->request($xml->toString)

}



child:

augment create_request => sub { #

my $self = shift;



my $xml = $self->_create_simple_node('request');

$xml->appendChild( $self->_create_extra_node('phone', $self->phone) );



$xml

};



How it works: in the create_request method (by the way, pay attention to its declaration. About this in part 2) there is a call to the "magic" function inner (). This is the part that can be changed. In the child class, we pass an anonymous sub to the augment modifier. Here it is executed in the place of inner () when the create_request is called on an instance of the child class.

At first glance, it seems tricky and useless. Tricky - yes, useless - no. To facilitate understanding, it is worthwhile to draw an analogy with the change of inherited attributes: the child inherits the method, but partially retains the parent functionality.

Just do not abuse this opportunity, because it reduces the "transparency" of the code.



Part 2. MooseX



Here the real interesting things begin) By the way, just in case I will say that MooseX is the module namespace for Moose extensions.



2.1 MooseX :: Declare


The module provides excellent syntactic sugar for Moose-based classes. With it, methods become methods, and classes become classes:

use MooseX::Declare;



class Business::Qiwi {

has trm_id => ( is => 'rw', isa => 'Str', required => 1, );

has password => ( is => 'rw', isa => 'Str', required => 1, );

has serial => ( is => 'rw', isa => 'Str', required => 1, );

#...

method get_balance() {

# ...

}

};



And you can and so:

class Business::Qiwi::Balance extends Business::Qiwi::Request {

# ...

};



A couple more important utility:

  1. no longer need to do my $self = shift; . Invocant is available in the method automatically (under the name $ self).
  2. classes declared with MooseX :: Declare automatically become immutable. Those. you no longer need to do __PACKAGE__->meta->make_immutable




2.2 MooseX :: Types


Creation of new data types (including on the basis of existing ones). Example:

use MooseX::Types -declare => [qw(Date EntriesList IdsList TxnsList BillsList)]; #

use MooseX::Types::Moose qw(Int Str ArrayRef HashRef); # Moose



subtype Date => as Str => where { /^\d{2}\.\d{2}\.\d{4}$/ } => message { 'Date must be provided in DD.MM.YYYY format' }; # Str



Using MooseX :: Types, you can leave out quotes for data type names:

subtype IdsList => as ArrayRef[Int] ; # has





2.3 MooseX :: Declare + MooseX :: Types


If you use the two modules mentioned above, you can do this:

method create_bill( Num $amount, Str $to, Str $txn, Str $comment, Bool $sms_notify?, Bool $call_notify?, Int $confirm_time? ) { # //

# ...

}



method get_bill_status( BillsList $bill ) { # !

# ...

}





2.4 MooseX :: MultiMethods


Adds the ability to overload (overloading) methods using the multi keyword. This piece of code from the tests to the module will perfectly say for me (hello, The Big Bang Theory):



use strict;

use warnings;

use Test::More tests => 3;



{

package Paper; use Moose;

package Scissors; use Moose;

package Rock; use Moose;

package Lizard; use Moose;

package Spock; use Moose;



package Game;

use Moose;

use MooseX::MultiMethod;



multi method play (Paper $x, Rock $y) { 1 }

multi method play (Paper $x, Spock $y) { 1 }

multi method play (Scissors $x, Paper $y) { 1 }

multi method play (Scissors $x, Lizard $y) { 1 }

multi method play (Rock $x, Scissors $y) { 1 }

multi method play (Rock $x, Lizard $y) { 1 }

multi method play (Lizard $x, Paper $y) { 1 }

multi method play (Lizard $x, Spock $y) { 1 }

multi method play (Spock $x, Rock $y) { 1 }

multi method play (Spock $x, Scissors $y) { 1 }

multi method play (Any $x, Any $y) { 0 }

}



my $game = Game->new;

ok($game->play(Spock->new, Scissors->new), 'Spock smashes Scissors');

ok(!$game->play(Lizard->new, Rock->new), 'Rock crushes Lizard');

ok(!$game->play(Spock->new, Paper->new), 'Paper disproves Spock');



1;





It was a brief review of Moose tweaks (and its extensions) - the new Perl5 object model.

In the bright future, perhaps, I will try to tell about roles as a substitute for inheritance. For this, let me leave.

Source: https://habr.com/ru/post/59542/



All Articles