perlbot - Collection de trucs et astuces pour Objets (the BOT)
L'ensemble suivant d'astuces et d'indications a pour intention d'attiser la curiosité sur des sujets tels que les variables d'instance et les mécanismes des relations entre objets et classes. Le lecteur est encouragé à consulter des manuels appropriés pour toute discussion sur les définitions et méthodologies de l'Orienté Objet. Ce document n'a pas pour but d'être un cours sur la programmation orientée objet ou un guide détaillé sur les caractéristiques orientées objet de Perl ni être interprété comme un manuel. Si vous cherchez des tutoriels, regardez du côté de la page de manuel perlboot, la page de manuel perltoot et la page de manuel perltooc.
La devise de Perl reste : il existe plus d'une manière de le faire.
Un tableau anonyme ou une table de hachage anonyme peuvent être utilisés pour conserver des variables d'instance. Des paramètres nommés sont également expliqués.
package Foo;
sub new { my $type = shift; my %params = @_; my $self = {}; $self->{'High'} = $params{'High'}; $self->{'Low'} = $params{'Low'}; bless $self, $type; }
package Bar;
sub new { my $type = shift; my %params = @_; my $self = []; $self->[0] = $params{'Left'}; $self->[1] = $params{'Right'}; bless $self, $type; }
package main;
$a = Foo->new( 'High' => 42, 'Low' => 11 ); print "High=$a->{'High'}\n"; print "Low=$a->{'Low'}\n";
$b = Bar->new( 'Left' => 78, 'Right' => 40 ); print "Left=$b->[0]\n"; print "Right=$b->[1]\n";
Une variable scalaire anonyme peut être utilisée quand seulement une variable d'instance est nécessaire.
package Foo;
sub new { my $type = shift; my $self; $self = shift; bless \$self, $type; }
package main;
$a = Foo->new( 42 ); print "a=$$a\n";
Cet exemple illustre comment on peut hériter des variables d'instance à partir d'une classe de base afin de permettre l'inclusion dans une nouvelle classe. Ceci nécessite l'appel du constructeur d'une classe de base et l'addition d'une variable au nouvel objet.
package Bar;
sub new { my $type = shift; my $self = {}; $self->{'buz'} = 42; bless $self, $type; }
package Foo; @ISA = qw( Bar );
sub new { my $type = shift; my $self = Bar->new; $self->{'biz'} = 11; bless $self, $type; }
package main;
$a = Foo->new; print "buz = ", $a->{'buz'}, "\n"; print "biz = ", $a->{'biz'}, "\n";
Ce qui suit illustre comment on peut mettre en effet les relations de « contenant » et « d'utilisation » entre objets.
package Bar;
sub new { my $type = shift; my $self = {}; $self->{'buz'} = 42; bless $self, $type; }
package Foo;
sub new { my $type = shift; my $self = {}; $self->{'Bar'} = Bar->new; $self->{'biz'} = 11; bless $self, $type; }
package main;
$a = Foo->new; print "buz = ", $a->{'Bar'}->{'buz'}, "\n"; print "biz = ", $a->{'biz'}, "\n";
L'exemple suivant illustre comment remplacer une méthode de classe de base puis appeler la méthode contournée. La SUPER pseudo-classe permet au programmeur d'appeler une classe contournée sans connaître vraiment où cette méthode est définie.
package Buz; sub goo { print "here's the goo\n" }
package Bar; @ISA = qw( Buz ); sub google { print "google here\n" }
package Baz; sub mumble { print "mumbling\n" }
package Foo; @ISA = qw( Bar Baz );
sub new { my $type = shift; bless [], $type; } sub grr { print "grumble\n" } sub goo { my $self = shift; $self->SUPER::goo(); } sub mumble { my $self = shift; $self->SUPER::mumble(); } sub google { my $self = shift; $self->SUPER::google(); }
package main;
$foo = Foo->new; $foo->mumble; $foo->grr; $foo->goo; $foo->google;
Notez bien que SUPER
fait référence aux superclasses du paquetage courant
(FOO
) et non aux superclasses de $self
.
Cet exemple décrit une interface pour la classe SDBM. Ceci crée une relation « d'utilisation » entre la classe SDBM et la nouvelle classe Mydbm.
package Mydbm;
require SDBM_File; require Tie::Hash; @ISA = qw( Tie::Hash );
sub TIEHASH { my $type = shift; my $ref = SDBM_File->new(@_); bless {'dbm' => $ref}, $type; } sub FETCH { my $self = shift; my $ref = $self->{'dbm'}; $ref->FETCH(@_); } sub STORE { my $self = shift; if (defined $_[0]){ my $ref = $self->{'dbm'}; $ref->STORE(@_); } else { die "Cannot STORE an undefined key in Mydbm\n"; } }
package main; use Fcntl qw( O_RDWR O_CREAT );
tie %foo, "Mydbm", "Sdbm", O_RDWR|O_CREAT, 0640; $foo{'bar'} = 123; print "foo-bar = $foo{'bar'}\n";
tie %bar, "Mydbm", "Sdbm2", O_RDWR|O_CREAT, 0640; $bar{'Cathy'} = 456; print "bar-Cathy = $bar{'Cathy'}\n";
L'avantage des langages OO est la facilité avec laquelle l'ancien code peut utiliser le nouveau code. L'exemple suivant illustre d'abord comment on peut empêcher la réutilisation d'un code puis comment promouvoir la réutilisation du code.
Le premier exemple illustre une classe qui utilise un appel avec une syntaxe complète, d'une méthode afin d'accéder à la méthode privée BAZ(). Le second exemple démontrera qu'il est impossible de remplacer la méthode BAZ().
package FOO;
sub new { my $type = shift; bless {}, $type; } sub bar { my $self = shift; $self->FOO::private::BAZ; }
package FOO::private;
sub BAZ { print "in BAZ\n"; }
package main;
$a = FOO->new; $a->bar;
À présent nous essayons de remplacer la méthode BAZ(). Nous souhaiterions que FOO::bar() appelle GOOP::BAZ(), mais ceci ne peut pas se faire car FOO::bar() appelle explicitement FOO::private::BAZ().
package FOO;
sub new { my $type = shift; bless {}, $type; } sub bar { my $self = shift; $self->FOO::private::BAZ; }
package FOO::private;
sub BAZ { print "in BAZ\n"; }
package GOOP; @ISA = qw( FOO ); sub new { my $type = shift; bless {}, $type; }
sub BAZ { print "in GOOP::BAZ\n"; }
package main;
$a = GOOP->new; $a->bar;
Afin de créer un code réutilisable, nous devons modifier la classe FOO, en écrasant la classe FOO::private. L'exemple suivant présente une classe FOO réutilisable qui permet à la méthode GOOP::BAZ() d'être utilisée à la place de FOO::BAZ().
package FOO;
sub new { my $type = shift; bless {}, $type; } sub bar { my $self = shift; $self->BAZ; }
sub BAZ { print "in BAZ\n"; }
package GOOP; @ISA = qw( FOO );
sub new { my $type = shift; bless {}, $type; } sub BAZ { print "in GOOP::BAZ\n"; }
package main;
$a = GOOP->new; $a->bar;
Utilisez l'objet afin de résoudre les problèmes de package et de contexte de classe. Tout ce dont une méthode a besoin doit être disponible par le biais de l'objet ou être transmis comme paramètre à la méthode.
Une classe aura parfois des données statiques ou globales qui devront être utilisées par les méthodes. Une classe dérivée peut vouloir remplacer ces données par de nouvelles. Lorsque ceci arrive, la classe de base peut ne pas savoir comment trouver la nouvelle copie de la donnée.
Ce problème peut être résolu en utilisant l'objet pour définir le contexte de la méthode. Laissez la méthode chercher dans l'objet afin de trouver une référence à la donnée. L'autre alternative est d'obliger la méthode d'aller à la chasse à la donnée (« est-ce dans ma classe ou dans une classe dérivée ? Quelle classe dérivée ? »), mais ceci peut être gênant et facilitera le piratage. Il est préférable de laisser l'objet indiquer à la méthode où la donnée est située.
package Bar;
%fizzle = ( 'Password' => 'XYZZY' );
sub new { my $type = shift; my $self = {}; $self->{'fizzle'} = \%fizzle; bless $self, $type; }
sub enter { my $self = shift;
# Ne cherchez pas à deviner si on devrait utiliser %Bar::fizzle # ou %Foo::fizzle. L'objet sait déjà lequel # on doit utiliser, donc il n'y a qu'à demander. # my $fizzle = $self->{'fizzle'};
print "The word is ", $fizzle->{'Password'}, "\n"; }
package Foo; @ISA = qw( Bar );
%fizzle = ( 'Password' => 'Rumple' );
sub new { my $type = shift; my $self = Bar->new; $self->{'fizzle'} = \%fizzle; bless $self, $type; }
package main;
$a = Bar->new; $b = Foo->new; $a->enter; $b->enter;
Un constructeur héritable doit utiliser le deuxième forme de bless()
qui permet de lier directement dans une classe spécifique. Notez dans
cet exemple que l'objet sera un BAR et non un FOO bien que le
constructeur soit dans la classe FOO.
package FOO;
sub new { my $type = shift; my $self = {}; bless $self, $type; }
sub baz { print "in FOO::baz()\n"; }
package BAR; @ISA = qw(FOO);
sub baz { print "in BAR::baz()\n"; }
package main;
$a = BAR->new; $a->baz;
Quelques classes, comme SDBM_File, ne peuvent pas être sous-classées correctement car elles créent des objects externes. Ce genre de classe peut être prolongée avec quelques techniques comme la relation « d'utilisation » mentionnée plus haut ou par délégation.
L'exemple suivant illustre une délégation utilisant une fonction AUTOLOAD()
afin d'accomplir un renvoi de message. Ceci permettra à l'object Mydbm
de se conduire exactement comme un objet SDBM_File.
package Mydbm;
require SDBM_File; require Tie::Hash; @ISA = qw(Tie::Hash);
sub TIEHASH { my $type = shift; my $ref = SDBM_File->new(@_); bless {'delegate' => $ref}; }
sub AUTOLOAD { my $self = shift;
# L'interpréteur Perl place le nom # du message dans une variable appelée $AUTOLOAD.
# Un message de DESTRUCTION (DESTROY) ne doit jamais être exporté. return if $AUTOLOAD =~ /::DESTROY$/;
# Enlève le nom du package. $AUTOLOAD =~ s/^Mydbm:://;
# Passe le message au délégué. $self->{'delegate'}->$AUTOLOAD(@_); }
package main; use Fcntl qw( O_RDWR O_CREAT );
tie %foo, "Mydbm", "adbm", O_RDWR|O_CREAT, 0640; $foo{'bar'} = 123; print "foo-bar = $foo{'bar'}\n";
L<perlboot>, L<perltoot> et L<perltooc>.
Cette traduction française correspond à la version anglaise distribuée avec perl 5.10.0. Pour en savoir plus concernant ces traductions, consultez http://perl.enstimac.fr/.
Sébastien Joncheray <info@perl-gratuit.com>. Mis à jour Paul Gaborit (Paul.Gaborit @ enstimac.fr).
Simon Washbrook <swashbrook@compuserve.com>