Inspiré par la visite de mes parents à la fin de la semaine, j’ai pensé écrire sur la façon dont les classes Perl peuvent également avoir des « parents », dont ils héritent des méthodes. Bien qu’il puisse sembler à première vue qu’il existe plusieurs façons de procéder, ces techniques partagent toutes le même mécanisme sous-jacent.
Les classes Perl sont juste réutilisées package
s, c’est-à-dire un espace de noms pour les variables et les sous-programmes. Les deux principales différences sont :
Si vous vouliez tout faire à la main au niveau le plus bas, vous pourriez créer une sous-classe au moment de la compilation comme ceci :
package Local::MyChildClass;
BEGIN { # don't do this:
require Local::MyParentClass;
push @ISA, 'Local::MyParentClass';
}
Ne le faites pas, car nous avons…
En 1997, Perl 5.004_04 a introduit le pragma (à l’époque où Perl utilisait ce type de schéma de version ; à l’époque du contrôle de version sémantique, nous l’appelions la version 5.4.4). Il fait ce qui précède BEGIN
bloc en une seule ligne :
use base 'Local::MyParentClass'; # don't do this unless you're also using fields
Vous pourriez voir use base
dans un code plus ancien, surtout s’il utilise également le fields
pragma. Cependant, les développeurs Perl déconseillent les deux car le premier fait taire certaines erreurs de chargement de module tandis que le second est en contradiction avec le principe de programmation orienté objet de l’encapsulation.
Donc use parent
à la place, que Perl a inclus depuis la version 5.10.1 en 2009 :
use parent 'Local::MyParentClass';
Il y a quelques années, mon collègue de Newfold Digital, David Oswald, a créé un fork de parent appelé parent::versioned qui prend en charge la spécification de la version la plus basse pour les superclasses. Tu l’appelles ainsi :
use parent::versioned ['Local::MyParentClass' => 1.23];
Dans un système OO
Il existe des dizaines de systèmes de programmation orientés objet sur CPAN qui fournissent du sucre syntaxique et des fonctionnalités supplémentaires aux bases minimales mais flexibles de Perl. Deux des plus populaires, Moose et Moo, offrent un extends
mot-clé que vous devriez utiliser à la place de use parent
afin que vos sous-classes puissent profiter de leurs fonctionnalités :
package Local::MyChildClass;
use Moo;
extends 'Local::MyParentClass';
Moose peut également spécifier une version de superclasse requise :
package Local::MyChildClass;
use Moose;
extends 'Local::MyParentClass' => {-version => 1.23};
Utilisez également le module MooseX::NonMoose lors de l’extension de classes non Moose, encore une fois afin d’obtenir les fonctionnalités de Moose même si vos méthodes proviennent d’ailleurs :
package Local::MyMooseClass;
use Moose;
use MooseX::NonMoose;
extends 'Local::MyPlainParentClass';
Le module expérimental Object::Pad spécifie une seule superclasse tout en définissant le nom de la classe avec une version optionnelle. Selon la disposition de fichier suggérée par l’auteur, y compris une version minimale requise, cela ressemblerait à :
use Object::Pad 0.41;
package Local::MyChildClass;
class Local::MyChildClass isa Local::MyParentClass 1.23;
Object::Pad et Corinna, son inspiration, sont des travaux en cours donc cette syntaxe n’est pas figée. Le concepteur de ce dernier, Curtis « Ovid » Poe, a écrit plus tôt cette semaine sur l’idée d’envisager une syntaxe plus cohérente.
Pour citer la documentation Perl, « l’héritage multiple indique souvent un problème de conception, mais Perl vous donne toujours assez de corde pour vous pendre si vous le demandez. » Toutes les techniques décrites ci-dessus, à l’exception de Object::Pad, prennent en charge l’héritage multiple en spécifiant une liste de superclasses. Par exemple:
package Local::MyChildClass;
use parent qw(Local::MyParentClass1 Local::MyParentClass2);
Si vous utilisez des rôles à la place ou au-dessus des superclasses (j’ai vu les deux situations) et que votre système OO ne les prend pas en charge tout seul, vous pouvez utiliser le module Role::Tiny, d’abord en décrivant votre rôle dans un paquet puis le consommer dans un autre :
package Local::DoesSomething;
use Role::Tiny;
...
1;
package Local::MyConsumer;
use Role::Tiny::With;
with 'Local::DoesSomething';
...
1;
Moo::Role utilise Role::Tiny sous le capot et Moo peut composer des rôles à partir de l’un ou l’autre. La syntaxe pour Moo et Moose est similaire :
package Local::DoesSomething;
use Moo::Role; # or "use Moose::Role;"
...
1;
package Local::MyConsumer;
use Moo; # or "use Moose;"
with 'Local::DoesSomething';
...
1;
Object::Pad spécifie les rôles avec le role
mot-clé, et les classes et les rôles utilisent does
pour les consommer :
use Object::Pad 0.56;
package Local::DoesSomething;
role Local::DoesSomething does Local::DoesSomethingElse;
...
1;
use Object::Pad 0.56;
package Local::MyConsumer;
class Local::MyConsumer does Local::DoesSomething;
...
1;
La mise en garde précédente concernant les modifications possibles de cette syntaxe s’applique.
Tel parent, (en quelque sorte) tel enfant
Bien sûr, tout l’intérêt de l’héritage ou de la consommation de rôle est que votre classe enfant ou consommateur puisse réutiliser des fonctions et des méthodes. Chacune des techniques ci-dessus a ses manières de remplacer ce code, à partir du Perl intégré SUPER
pseudo-classe à Moose’s et super
mots-clés, aux modificateurs de méthode de Moose et de Moo. (Vous pouvez utiliser ce dernier en dehors de Moo car il est fourni par Class::Method::Modifiers.)
J’ai déjà écrit sur le choix entre les méthodes de substitution et de modification, et en ce qui concerne le code Moose et Moo, je suis maintenant du côté de l’utilisation du around
modificateur de méthode si une méthode doit appeler une méthode héritée ou consommée du même nom. Object::Pad n’a pas (encore) de modificateurs de méthode, donc class
es qui l’utilisent devront se contenter de SUPER
dans leurs method
s avec un :override
attribut qui générera une erreur si un parent ne fournit pas également la même méthode.
L’écharpe des parents
En fin de compte, votre choix de système Perl OO déterminera comment (ou si) vous gérez l’héritage et peut même être un facteur décisif. Lequel choisiriez-vous? Et plus important encore, ai-je rendu mes parents fiers avec ce post ?