Cours gratuits » Cours informatique » Cours programmation » Cours Perl » Cours notions de base pour la programmation en Perl

Cours notions de base pour la programmation en Perl

Problème à signaler:

Télécharger



★★★★★★★★★★3.5 étoiles sur 5 basé sur 1 votes.
Votez ce document:

Cours notions de base pour la programmation en Perl

Programmation objet

La programmation objet est le concept nouveau de ces vingt dernières années. C++ est bâti sur le C et apporte l'objet. Java a été mis au point (entre autres) pour passer outre les nombreux pièges et problèmes de C++. Ruby est un langage interprété basé sur ce concept objet.

Perl, qui utilisait un garbage collector bien avant que Java n'existe, ne pouvait pas être en reste et la communauté Perl a rapidement proposé les extensions du la

ngage nécessaires à ce type de programmation.

On notera que ces extensions sont peu nombreuses, car l'idée a été de réutiliser au maximum ce qui existait déjà en Perl et de l'appliquer à la programmation objet. Le C++ étant compilé et devant rester compatible avec le C, cela fut un challenge de mettre sur pied ce langage ; cela explique sans doute pourquoi C++ est si complexe et comporte tant de pièges. Perl, de par sa nature interprétée, n'a pas posé de problème pour s'étendre à l'objet.

La programmation par objets ouvre le champ des possibilités offertes au programmeur ; alliée à un langage puissant et flexible comme Perl, elle offre la souplesse, la richesse et la facilité d'écriture qu'il manque aux langages uniquement objet. Toutefois, de par sa nature permissive, le langage Perl ne saurait être aussi strict que des langages exclusivement objet. Le programmeur est invité à faire les choses proprement, mais rien ne l'y oblige.

Vous avez dit objet ?

Sans revenir sur la théorie de la programmation objet, je vais tenter ici d'y faire une courte introduction. La programmation orientée objet est un type de programmation qui se concentre principalement sur les données. La question qui se pose en programmation OO (orientée objet) est "quelles sont les données du problème ?" à l'instar de la programmation procédurale par exemple, qui pose la question « quelles sont les fonctions/actions à faire ?nbsp;». En programmation OO, on parle ainsi d'objets, auxquels on peut affecter des variables/attributs (propriétés) et des fonctions/actions (méthodes).

On parle de « classenbsp;», qui est une manière de représenter des données et comporte des traitements : une classe « Chaussurenbsp;» décrit, par exemple, les caractéristiques d'une chaussure. Elle contient un champ décrivant la pointure, la couleur, la matière, etc. Une telle classe comporte de plus des traitements sur ces données ; ces traitements sont appelés « méthodesnbsp;». Grossièrement une méthode est une fonction appliquée à un objet.

Une fois définie une telle classe, il est possible d'en construire des instances : une instance d'une classe est dite être un objet de cette classe. Dans notre exemple, il s'agirait d'une chaussure dont la pointure, la couleur et la matière sont renseignées.

Préparatifs

Nous allons maintenant voir comment écrire une classe en Perl. Vous verrez, cela est très simple et démystifie la programmation objet.

En Perl, une classe n'est autre qu'un module et un objet (instance de cette classe) n'est autre qu'une référence associée à cette classe. Dans le constructeur, nous allons donc créer une référence (typiquement vers une table de hachage) et nous allons l'associer au package en question ; lors de cette association, on dit en Perl que l'on bénit (bless en anglais) la référence.

Les champs de l'objet seront en fait stockés dans cette table de hachage, sous forme de la clef pour le nom du champ et de la valeur pour la valeur du champ.

Voyons un exemple : définissons une classe Vehicule qui comporte deux champs : un nombre de roues et une couleur.

...

Manipulations de l'objet

Revenons à notre exemple. En plus de savoir qu'elle pointe vers une table de hachage, la référence $v sait de quelle classe elle est. En effet, si nous l'affichons :

print "$v\n";

Nous obtenons la chose suivante à l'écran :

Vehicule=HASH(0x80f606c)

Je vous rappelle que dans le cas de l'affichage d'une référence vers une table de hachage non bénie, nous obtenons quelque chose de la forme :

HASH(0x80fef74)

Un objet (à partir de maintenant, nommons ainsi une référence vers une table de hachage bénie) sait donc de quelle classe il est. Cela va lui permettre de choisir le bon package quand on appellera une méthode sur cet objet (lire la suite).

Voyons maintenant ce que donne le module Data::Dumper (dont j'ai déjà parlé) sur une telle référence :

use Data::Dumper;

print Dumper($v)."\n";

L'affichage suivant est effectué :

$VAR1 = bless( {

                 'COULEUR' => 'bleu',

                 'NB_ROUES' => 2

               }, 'Vehicule' );

On remarquera que la tradition de Data::Dumper qui consiste en ce que la chaîne renvoyée est directement intégrable dans un code Perl est respectée : même l'opération de bénédiction (bless) est présente.

Il faut bien voir que $v est une référence vers un objet. Autrement dit, si on fait une copie de cette variable

my $w = $v;

nous obtenons deux variables qui pointent vers le même objet.

...

Plusieurs constructeurs

Comment écrire plusieurs constructeurs pour une même classe ? Rien de plus simple. Le nom new que j'ai choisi pour le constructeur n'a rien d'obligatoire, en fait un constructeur est une simple fonction qui renvoie une référence bénie. Seule la syntaxe d'appel change par rapport à ce que l'on a vu pour les modules. Je peux donc écrire un autre constructeur (donc avec un autre nom) :

sub nouveau {

   my ($class,$couleur) = @_;

   my $this = {};

   bless($this, $class);

   $this->{COULEUR} = $couleur;

   return $this;

}

Ce constructeur de Vehicule prend par exemple un seul paramètre (la couleur) et n'affecte pas de champs NB_ROUES, car rien de ne l'y oblige. À moi de savoir comment je veux gérer mes véhicules, à savoir comment j'autorise qu'ils soient construits.

Pour être propre et cohérent, nous allons tout de même considérer qu'il est sage d'affecter une valeur à la clef NB_ROUES :

sub nouveau {

   my ($class,$couleur) = @_;

   my $this = {};

   bless($this, $class);

   $this->{NB_ROUES} = 0;

   $this->{COULEUR} = $couleur;

   return $this;

}

Voici comment on va faire appel à ce constructeur :

my $v2 = Vehicule->nouveau( "bleu" );

De la même façon que pour le précédent constructeur, il est possible d'utiliser la syntaxe suivante :

my $v2 = nouveau Vehicule( "bleu" );

Ce qui est, reconnaissons-le, quelque peu déroutant. C'est pour cela que je vous conseille d'utiliser plutôt la première syntaxe Vehicule->nouveau() ou alors de vous en tenir à un constructeur new pour écrire new Vehicule().

Écrire une méthode

Une méthode est une fonction qui s'applique à une instance de la classe. Cela est vrai dans tous les langages objet, mais bien souvent cela est masqué ; dans notre cas rien de masqué, le premier paramètre de la fonction sera l'objet (la référence bénie) :

sub roule {

   my ($this,$vitesse) = @_;

   print "Avec $this->{NB_ROUES} roues, je roule à $vitesse.\n";

}

Cette fonction déclarée dans le fichier Vehicule.pm a donc pour premier paramètre l'objet sur lequel elle est appelée. Vous noterez une fois de plus que rien n'oblige le programmeur à nommer cette variable $this ; la seule contrainte est sa première place parmi les arguments.

Cette méthode peut dès maintenant être appelée depuis le fichier script.pl sur les objets de type Vehicule. Il n'y pas de nécessité de faire usage du lourd mécanisme d'Exporter, car nous n'avons pas besoin de modifier l'espace de nom des fonctions des scripts appelants.

Pour appeler la méthode, il suffit d'écrire dans le fichier script.pl :

$v->roule( 15 );

La fonction appelée sera celle qui a pour nom roule définie dans le package lié à la référence $v lors de sa bénédiction.

L'affichage suivant a donc lieu :

Avec 2 roues, je roule à 15.

Écrivons par exemple une méthode d'affichage :

sub toString {

   my ($this) = @_;

   return "(Vehicule:$this->{NB_ROUES},$this->{COULEUR})";

}

Cette méthode renvoie une chaîne de caractères, représentation de l'objet. Les habitués de Java noteront que j'ai choisi le nom de cette fonction pour leur rappeler des souvenirs, mais qu'il n'a rien de spécial. Voici comment l'utiliser dans le script :

print $v->toString()."\n";

Et l'affichage a lieu :

(Vehicule:2,bleu)

Libre à vous de choisir un plus bel affichage.

Reparlons des champs

Un champ est une donnée propre à une instance de classe. En Perl, ces champs sont stockés comme clef/valeur dans la table de hachage qui constitue l'objet (si on utilise une table de hachage comme objet, ce qui est souvent le cas). Nos véhicules comportent deux champs : NB_ROUES et COULEUR.

Ces champs étant de simples clef/valeur d'une table de hachage dont on dispose d'une référence dans le script, ils y sont accessibles. Dans le script, nous pouvons écrire dans le fichier script.pl :

foreach my $k (keys %$v) {

   print "$k : $v->{$k}\n";

}

C'est-à-dire que je peux accéder sans restriction à l'ensemble des champs de l'objet. En effet, j'ai en main une référence vers une table de hachage ; le fait qu'elle soit bénie ne change rien au fait que je peux la déréférencer et accéder aux diverses valeurs qu'elle contient. Je peux aussi bien modifier ces valeurs ou même en ajouter ou en supprimer, car il s'agit en effet d'une table de hachage comme les autres.

« Comment protéger  les données d'un objet ? », allez-vous alors me demander. Eh bien, Perl n'a rien prévu pour ça. Cela va sans doute faire hurler les puristes de la programmation objet, mais c'est comme cela... Perl vous propose les principaux mécanismes pour faire de la programmation objet tout en restant cohérent avec le reste du langage, certaines choses ne sont donc pas possibles.

Faute de champs privés en Perl, il existe une convention qui dit que les champs dont la clef commence par un underscore (souligné _) sont des champs privés et les scripts qui utilisent les objets ainsi faits sont priés de respecter cette convention. C'est le cas de beaucoup de modules CPAN. Cette convention est aussi valable pour les méthodes (une méthode dont le nom commence par un underscore est une méthode privée).

De façon générale, un programmeur Perl est quelqu'un de bonne éducation (sinon il programmerait en Java ;-))) et il ne modifiera pas une instance d'une classe qu'il n'a pas écrite. En effet, pourquoi modifier un objet Net::FTP alors qu'il fait très bien son travail ? De toute façon, il a forcément accès au code source de cette classe et s'il veut la modifier, il peut en faire une copie et la modifier !

La protection des données est donc plus une nécessité sociale (manque de confiance en l'espèce humaine à laquelle les développeurs prétendent encore faire partie :-))) qu'une nécessité technique. Pas dramatique pour faire de l'objet.

. Composition

Prenons le temps de faire un petit exemple pratique pour illustrer le concept de composition. La composition est le fait qu'un objet est constitué d'autres objets. Par exemple un garage comporte des véhicules.

Je décide qu'un garage aura une taille limite : il s'agira du nombre maximal de véhicules qu'il pourra contenir. Un garage devra donc contenir une liste de véhicules.

...

Destruction d'un objet

Un objet est détruit dès qu'aucune référence ne pointe vers cet objet. La ligne suivante, par exemple, libère la place mémoire occupée par l'objet Vehicule référencé par $v2 :

$v2 = undef;

À cet instant, Perl se rend compte que l'objet en question n'est plus accessible, la mémoire sera donc automatiquement libérée par le mécanisme du garbage collector.

La même chose a lieu dans le cas de la disparition d'une variable locale :

if( ... ) {

   my $v3 = Vehicule->new(3,'jaune');

   ...

   ...

}

La variable $v3 cesse d'exister à la fermeture de l'accolade du if. L'objet qui a été créé dans le bloc sera donc détruit (sauf si on a fait en sorte qu'une autre variable dont la visibilité dépasse ce bloc pointe aussi vers l'objet).

Cette libération n'est peut-être pas faite en temps réel, mais ce n'est pas au programmeur de s'en occuper.

Il existe une méthode très spéciale, dont le nom est réservé, qui est appelée lors de la destruction d'une instance d'un objet. Il s'agit de la méthode DESTROY. Cette méthode sera appelée (si elle existe dans la classe) par le garbage collector juste avant la libération de la mémoire de l'objet.

sub DESTROY {

   my ($this) = @_;

   print "À la casse Vehicule ! ";

   print "($this->{NB_ROUES} $this->{COULEUR})\n";

}

Cette méthode doit être définie dans le package de l'objet en question et reçoit en premier argument une référence vers l'objet qui va être détruit.

Cette méthode est très utile pour libérer des ressources (fichier, connexion réseau, etc.) qui ont été allouées lors de la création de l'objet.

Héritage

L'héritage est un des apports de la programmation objet. Perl dispose de tout ce qu'il faut pour le mettre en œuvre.

Imaginons qu'en plus de véhicules, nous avons besoin de manipuler des vélos et des voitures. Ces objets ont en commun d'avoir un nombre de roues et une couleur. Ils ont des caractéristiques supplémentaires qui leur sont propres ; les vélos ont un nombre de vitesses et les voitures, un nombre de sièges. Ces classes Velo et Voiture vont donc hériter de la classe Vehicule, c'est-à-dire qu'elles vont comporter tous les champs et les méthodes de cette classe.

On dit alors que la classe Vehicule est la classe mère ; les classes Velo et Voiture étant des classes filles. Notez bien que je prends comme exemple deux classes filles et qu'il n'est nullement nécessaire d'avoir deux classes filles pour mettre en œuvre l'héritage, une seule est suffisante.

Voyons le cas de la classe Velo, créons pour cela un fichier Velo.pm qui contient :

package Velo;

use strict;

use warnings;

use Vehicule;

our @ISA = qw(Vehicule);

# ... ici les méthodes qui vont suivre

1;

On signale le lien de filiation entre classes au moyen de la variable @ISA positionnée à la valeur d'une liste contenant le nom de la classe mère. ISA vient de l'anglais "is a", est un : on dit qu'un vélo est un véhicule. Il hérite donc des champs et méthodes de la classe Vehicule.

Définissons-lui un constructeur :

sub new {

   my ($class,$couleur,$nbVitesses) = @_;

   my $this = $class->SUPER::new( 2, $couleur );

   $this->{NB_VITESSES} = $nbVitesses;

   return bless($this,$class);

}

Ce constructeur prend donc deux paramètres. Il appelle le constructeur de la classe mère (syntaxe $class->SUPER::new). Il ajoute un champ NB_VITESSE et renvoie une référence bénie en Velo. Notez bien qu'aucun appel au constructeur de la classe mère n'est fait par défaut, il faut le faire explicitement dans tous les cas.

Le lecteur aura noté que, comme les champs de la classe mère et de la classe fille sont stockés dans la même table de hachage, il n'y a pas de moyen simple pour faire de surcharge ou de masquage des champs. Les noms des champs devront être minutieusement choisis pour ne pas entrer en conflit les uns avec les autres.

Voyons à présent comment écrire une méthode pour cet objet :

sub pedale {

   my ($this,$ici) = @_;

   print "Sur mon vélo $this->{COULEUR} ";

   print "je pédale avec $this->{NB_VITESSES} vitesses";

   print " dans $ici.\n";

}

Utilisons maintenant tout cela dans notre script :

use Velo;

my $velo = Velo->new('blanc',18);

$velo->pedale('les bois');

$velo->roule(10);

Un vélo dispose de la méthode roule, car il est aussi un véhicule. L'affichage suivant est effectué :

Sur mon vélo blanc je pédale avec 18 vitesses dans les bois.

Avec 2 roues, je roule à 10.

Voyons à présent comment afficher un tel objet. Nous laisserons le soin à la classe Vehicule d'afficher le vélo comme étant un véhicule et nous n'effectuerons dans la classe Velo que l'affichage de ce que nous avons ajouté à la classe mère :

sub toString {

   my ($this) = @_;

   my $s = "[Velo:$this->{NB_VITESSES}";

   $s .= $this->SUPER::toString();

   $s .= "]";

}

La syntaxe $this->SUPER::toString() correspond à l'appel de la méthode toString de la classe mère. Nous pouvons maintenant l'appeler dans notre script :

print $velo->toString()."\n";

L'affichage suivant est effectué :

[Velo:18(Vehicule:2,blanc)]

Rien de plus simple !

Seule chose pas extrêmement pratique, l'appel au destructeur s'arrête au premier destructeur rencontré. Si dans notre exemple nous définissions une méthode DESTROY pour la classe Velo, la méthode DESTROY de la classe Vehicule ne sera pas appelée. Cela peut être gênant si des ressources importantes sont libérées dans cette méthode ; il faut alors l'appeler explicitement. Voici un exemple de méthode DESTROY pour la classe Velo :

sub DESTROY {

   my ($this) = @_;

   $this->SUPER::DESTROY();

   print "Bye bye Velo ! ";

   print "($this->{NB_VITESSES} $this->{NB_ROUES} ".

         "$this->{COULEUR})\n";

}

La deuxième ligne de la méthode fait un appel à la méthode de la classe mère.

Pour faire de l'héritage multiple en Perl, rien de plus simple. Vous aurez peut-être noté que la variable @ISA est un tableau, elle peut donc contenir plusieurs noms de classe :

package Voiture;

our @ISA = qw(Vehicule Danger Pollution);

La détermination de la bonne méthode à appeler est effectuée dynamiquement par une recherche en profondeur dans l'arbre d'héritage. Je ne m'étendrai pas plus sur cette question de l'héritage multiple.

Classes d'un objet

Dans cette partie nous allons voir comment connaître la classe d'un objet ainsi que tester l'appartenance à une classe pour un objet.

Souvenez-vous de l'opérateur ref qui, appliqué à une référence, renvoie le type de structure de données vers laquelle elle pointe (scalaire, tableau, table de hachage, etc.). Appliqué à un objet, il renvoie la classe de l'objet :

print ref($velo)."\n";

print "Ouf, tout va bien !\n"

   if( ref($velo) eq "Velo" );

À chaque appel au constructeur de cette classe, la variable $nbVehicules sera donc incrémentée.

Maintenant, comment écrire une méthode statique ? Une méthode statique (ou méthode de classe) est une méthode qui n'est pas appelée sur une instance de la classe (donc pas de variable $this), mais pour toute la classe. Ce n'est ni plus ni moins qu'une brave fonction présente dans le package. Cette fonction pourra donc uniquement accéder aux champs statiques de la classe.

Nous pourrions, par exemple, écrire une méthode statique qui renvoie le nombre de véhicules créés (variable $nbVehicules) :

sub getNbVehicules {

   my ($class) = @_;

   return $nbVehicules;

}

On notera que la méthode prend en premier argument le nom de la classe. Cela a pour conséquence que l'appel à la méthode ne se fait pas tout à fait comme pour une fonction d'un package (comme vu pour les modules), mais de la manière suivante :

print Vehicule->getNbVehicules()."\n";

Le nom de la classe est suivi d'une flèche, du nom de la méthode et des éventuels arguments entre parenthèses. N'écrivez pas Vehicule::getNbVehicules(), car le nom de la classe n'est pas transmis et surtout car les mécanismes d'héritage ne sont pas mis en œuvre. S'il est possible d'écrire Velo->getNbVehicules(), il n'est pas permis d'écrire Velo::getNbVehicules().

Le lecteur notera que les constructeurs sont des méthodes statiques. Ils retournent des références bénies, mais n'ont rien de particulier par rapport à d'autres méthodes de classe.

Il est tout à fait possible d'appeler cette méthode sur une instance de la classe Vehicule

print $v->getNbVehicules()."\n";

mais dans ce cas le premier argument reçu n'est pas le nom de la classe mais l'objet en question (c'est donc une méthode d'instance et de classe...). Cela ne change rien pour notre méthode getNbVehicules car elle n'utilise pas son premier argument, mais le cas est gênant pour les constructeurs qui ont à bénir une référence. Pour cela, tout constructeur devrait commencer par déterminer s'il a en premier argument le nom de la classe ou une référence.


32