Cet article est la troisième partie d’une série consacrée à Moose qui vise à montrer comment écrire du Perl Objet moderne. Les exemples de code sont tous testés avec l’excellent shell Devel::REPL.
Dans notre précédent article, nous avons vu comment spécialiser une classe en créant la classe Barbare à partir de la classe Heros. Aujourd’hui nous allons découvrir un autre mécanisme important que Moose nous permet de réaliser : l’interfaçage. Aujourd’hui, nous allons jouer avec les Moose::Role.
Moose nous permet de définir des interfaces au moyen de la classe Moose::Role. Nous allons utiliser ce mécanisme pour armer Conan. Maintenant qu’il sait se battre, crier et encaisser des coups, il lui faut une arme ! Regardez-le avec son regard aggard, un barbare à mains nues ça ne ressemble à rien !
Un rôle en Moose est une classe particulière, qui ne peut pas être instanciée et qui spécifie un modèle. D’autres classe – instanciables – pourront implémenter ce rôle et devront par là-même respecter les restrictions de ce dernier.
Nous allons donc créer une rôle « Arme » qui imposera à toutes les armes dignes de ce nom (les classes Moose qui implémenteront le Moose::Role « Arme ») de fournir une méthode degats().
Ensuite nous modifierons la classe Heros pour que chaque Heros (et pas uniquement les barbares) puisse disposer d’une arme.
Commençons par la fin, et modifions la classe Heros (définie dans le premier article de cette série). Nous devons pouvoir prendre en compte la présence d’une arme, il nous faut donc un nouvel attribut pour stocker cet objet :
package Heros;
[...]
has 'arme' => (
is => 'rw',
does => 'Arme',
predicate => 'has_arme'
);
Deux notions nouvelles ici : does, qui nous permet d’éxiger que l’objet contenu dans cet attribut implémente le role « Arme » et predicate qui nous laisse définir le nom d’une méthode dédiée à tester la présence de l’attribut ($heros->has_arme renverra vrai si une arme est définie, faux sinon).
Maintenant, nous voulons que notre arme soit prise en compte lors d’une attaque au cas où le héros en possède une. Nous allons pour cela utiliser la méthode définie dans le prédicat de l’attribut :
package Heros;
[...]
sub attaque
{
my ($self) = @_;
my $bonus = 0;
if ($self->has_arme) {
$bonus = $self->arme->degats;
}
return $self->force + $bonus + int(rand(10) + 1);
}
Bien, on constate que maintenant, l’attaque d’un héros prend en compte les dégâts de l’arme, si celui-ci en possède une. Ces dégâts sont obtenus en appelant la méthode degats() de l’arme. Il nous faut donc être certains que l’objet qui tient rôle d’arme implémente bien une méthode de ce nom. Et c’est là qu’intervient Moose::Role :
package Arme; use Moose::Role; requires 'degats'; 1;
Tout simplement. Ici nous déclarons un Moose::Role nommé Arme qui spécifie une chose : toutes les armes doivent proposer une méthode degats().
Il ne nous reste plus qu’à créer notre première arme. J’avais pensé commencer avec un gourdin mais quand j’ai vu la tête de Conan, j’ai compris que c’était une mauvaise idée… Hahem. Bref, créons un glaive donc.
package Glaive;
use Moose;
with ('Arme');
sub degats
{
return 3;
}
1;
Ici, le mot-clef with permet de déclarer que notre classe implémente le role Arme. Cela nous impose donc de proposer une méthode degats (le lecteur pourra s’amuser à reproduire l’exemple sans la méthode en question pour voir ce qui se passe).
Bien, maintenant, tout semble en place. Jouons un peu avec toutes ces nouveautés. Comme nous avons modifié la classe Heros et que Barbare en hérite, nous devons avoir le support des armes avec un Barbare, vérifions cela avec conan :
Perl> my $conan = new Barbare name => 'Conan' $Barbare1 = Barbare=HASH(0x8ada0a4); Perl> $conan->has_arme 0
On a bien le prédicat has_arme ajouté par le nouvel attribut arme de la classe Heros. Maintenant, testons la validité de nos propos en essayant de faire une aberration en attribuant un Heros comme arme pour conan (bon d’accord, on pourrait y voir une certaine logique…)
Perl> $conan->arme(new Heros name => 'Bilbon') Runtime error: Attribute (arme) does not pass the type constraint (__ANON__) with Heros=HASH(0x8ae87d0)
En effet, dans notre modèle, un Heros n’est pas une arme (du moins pas encore, pauvre Bilbon) et l’attribut arme ne se laisse pas abuser aussi facilement. Conan n’aura qu’à bien se tenir et utiliser un glaive.
Perl> $conan->arme(new Glaive) $Glaive1 = Glaive=HASH(0x8ada074);
C’est mieux. Les attaques de Conan vont maintenant être augmentées de 3 points grâce au glaive. Laissons le mot de la fin à Conan, fier qu’il est de sa nouvelle acquisition :
Perl> $conan->attaque Ourgh! 14 Perl> $conan->attaque Mabangaaa! 16 Perl> $conan->attaque Yeeeeaaa! 13
Dans notre prochain article, nous découvrirons la puissance de la coercition d’attribut en créant la classe « Mage ».