Lucrarea-7

Lucrarea 7

Module, Clase şi Obiecte (partea I)

Introducere

package Modul;

sub test { "test"; }

return 1;

Un modul în PERL este o colecţie de subrutine. Simplu. De obicei modulele se scriu în fişiere separate cu extensia .pm. Modulele sunt de fapt pachete PERL, sintaxa pentru declararea unui pachet plus modul de utilizare este prezentat mai jos:

package Pachet;

sub returneaza_string {

return "String impachetat";

}

return 1;

use lib ".";

# adauga directorul curent in

# lista directoarelor

# in care interpretorul cauta

# module

# observati ca extensia .pm se

# omite.

use Pachet;

print Pachet::returneaza_string;

Un obiect în PERL este o colecţie de referinţe la date dintr-o clasă. O clasă este de fapt un modul. Orice modul trebuie să returneze o valoare adevarată, de obicei valoarea 1. Acest lucru este absolut necesar, interpretorul va genera erori în cazul în care modulul nu returnează "true" (1).

În exemplul următor se declară o clasă "CoffeeMachine" care va simula funcţionarea unui automat de cafea ;). În această clasă apare momentan doar constructorul. Se va observa că metodele dintr-un obiect sunt de fapt subrutine.

La apelare fiecare metodă, în afară de constructor, primeşte ca prim parametru o referinţă la instanţa obiectului din care face parte (ca să ştie şi ea din ce instanţă face parte :)).

package CoffeeMachine;

sub new {

my $self = {

'coffee' => 10,

'sugar' => 20,

'money' => 0,

};

# Se creeaza o referinta la

# structura de date

# care contine proprietatile

# obiectului.

# Prin aceasta initializam

# cateva proprietati.

# Alte proprietati se

# pot adauga pe parcurs

# in metode de initializare,

# setare sau resetare.

print "CoffeMachine ON.\n";

bless $self;

return $self;

}

return 1;

# Exemplul de utilizare a clasei

# CoffeeMachine

# cu trei modalitati de apelare

# a constructorului.

use lib ".";

use CoffeeMachine;

my $cm_var1 = new CoffeeMachine;

my $cm_var2 = CoffeeMachine::new();

my $cm_var3 = CoffeeMachine->new();

Un constructor trebuie să returneze o referinţă la obiectul (instanţa) pe care tocmai l-a creat. Un alt mod de a spune... un constructor trebuie să lipească o referinţă la o clasă. Acest lucru se realizează cu ajutorul funcţiei bless.

Procedeul se numeşte "blessing". Putem să vedem acest procedeu ca un fel de botez :). Un copil (instanţă obiect) după botez va purta un prenume unic dar va fi asociat părintilor lui (familiei), în cazul nostru... clasei, prin numele de familie.

Pentru a putea fi moştenită o clasă trebuie să folosească sintaxa completă a funcţiei bless (bless $self, $class) care, pe lângă parametrul obiect mai primeşte şi un parametru "nume de clasă". În lipsa acestui parametru, bless "lipeşte" obiectul la numele clasei curente.

Pentru a putea apela obiectul prin referinţă ($cm->new(); nu doar CoffeeMachine->new();) testăm parametrul preluat în constructor (ref($proto)) şi setăm clasa ($class) ca atare. Dacă încă mai sunteţi aici încercaţi exemplul de mai jos şi observaţi ce se petrece în constructor cu clase, referinţe si referinţe la clase :).

În exemplul următor se modifică metoda de iniţializare a parametrilor prin folosirea unui apel la o metodă de iniţializare.

package CoffeeMachine;

sub new {

my $proto = shift;

my $class = ref($proto) || $proto;

# se creaza o referinta

# la structura de date goala

my $self = {};

print "$class ON.\n";

bless $self, $class;

$self->InitMachine();

return $self;

}

sub InitMachine {

my $self = shift;

$self->{coffee} = 10;

$self->{sugar} = 20;

$self->{warm} = 0;

print __PACKAGE__ . " Init OK.\n";

}

sub CheckWarmUp {

my $self = shift;

print "Checking warmup: ";

if ($self->{warm}) {

print "WARM\n";

}

else {

print "COLD\n";

}

return $self->{warm};

}

sub WarmUp {

my $self = shift;

print "Warming Up...";

my $timer = 5;

while ($timer) {

print $timer--, " ";

sleep(1);

}

print " DONE.\n";

$self->{warm} = 1;

}

return 1;

# vezi lista

# de variabile speciale

# de la sfarsit

$| = 1;

use lib ".";

use CoffeeMachine;

use Data::Dumper;

my $cm = new CoffeeMachine;

#print Dumper($cm);

if (not $cm->CheckWarmUp) {

$cm->WarmUp();

}

#print Dumper($cm);

$cm->CheckWarmUp

Exercitiu:

Completaţi clasa CoffeeMachine începută în lucrarea de mai sus adăugând metode pentru cumpărare şi afişare de status.

Module, Clase şi Obiecte (partea II)

Moştenire

Conceptul de moştenire la obiectele PERL se poate aplica în mai multe moduri.

În continuare se va prezenta pe larg un singur mod de aplicare, modul care funcţionează în toate condiţiile (chiar şi cu use strict;) şi care este şi cel mai intuitiv.

Fiecare pachet PERL poate avea setul său de variabile (proprietăţi) chiar şi variable cu acelaşi nume, fără a interfera unele cu altele.

package Unu;

$nume = "Acesta este pachetul Unu";

package Doi;

$nume = "Acesta este pachetul Doi";

package main;

print("$Unu::nume\n");

print("$Doi::nume\n");

Returnează:

Acesta este pachetul Unu

Acesta este pachetul Doi

Observaţi că deşi în ambele module se foloseşte numele de variabilă "$nume" rezultatul este diferit pentru fiecare apel. Operatorul :: este separatorul de "namespace". În acest caz operatorul -> nu va funcţiona deoarece aici nu avem de-a face cu un obiect ci doar cu două module din namespace-uri diferite.

Moştenirea funcţionează plasând în vectorul @ISA lista de clase părinte pe care o clasa copil trebuie să o moştenească. La apel în cod interpretorul caută toate metodele (apelate) care lipsesc în definirea clasei copil printre clasele enumerate în @ISA de la stânga la dreapta.

O clasă specială, numită clasa UNIVERSAL este adaugată automat la capătul listei @ISA, deci toate clasele moştenesc implicit clasa UNIVERSAL.

package Unu;

sub test {

print("Test din Unu\n");

}

package Doi;

@ISA = (Unu); # specifica pe Unu ca parinte

package UNIVERSAL; #clasa speciala

sub AUTOLOAD {

die("ATENTIE: functia $AUTOLOAD lipseste. Parametri: @_\n");

}

package main;

Doi->test();

Doi->bla("xyz","abc");

Rezultat:

Test din Unu

ATENTIE: functia Doi::bla lipseste. Parametri: xyz abc

Clasa "Doi" moştenteşte pe "Unu", implicit şi metoda "test". Se observă că această metodă poate fi apelată prin obiectul "Doi", metoda "bla" nu se găseşte nici în "Unu" nici în "Doi". Aici intervine pachetul UNIVERSAL cu metoda AUTOLOAD. Acestă metodă este automat apelată când nu se găseşte metoda căutată în clasa copil sau în clasele moştenite, trecute în @ISA.

Varianta de mai sus nu este acceptată dacă se foloseşte use strict;. În acest caz se foloseşte construcţia use base qw(Modul1, Modul2). (vezi exemplul următor).

În exemplul următor se continuă clasa CoffeeMachine, aceasta fiind moştenită de două clase "Tea" şi "Chocolate". Aceste clase moştenesc clasa CoffeeMachine şi folosesc funcţia Identify şi WarmUp. În fiecare dintre aceste două clase copil apelează metoda Identify în mod diferit, astfel exemplificându-se construcţia my $class = ref($proto) || $proto de mai sus. Se vor observa cele două moduri de apel ale metodelor şi anume metoda de apel prin numele clasei (în Tea.pm) şi metoda prin referiţă (în Chocolate.pm). Această construcţie se găseşte în metoda Identify.

Construcţia:

package Birou;

#mosteneste pachetele Cladire si Cartier

use base qw(Cladire Cartier);

Este echivalentă cu construcţia următoare:

package Birou;

BEGIN {

our @ISA = qw(Cladire Cartier);

require Cladire;

require Cartier;

}

Se prezintă în continuare clasa CoffeeMachine şi clasele produs Tea şi Chocolate.

#CoffeeMachine.pm

#---------------------

package CoffeeMachine;

use strict;

use warnings;

sub new {

my $proto = shift;

my $class = ref($proto) || $proto;

my $self = {};

print "$class ON.\n";

bless $self, $class;

$self->InitMachine();

$self->Identify();

return $self;

}

sub InitMachine {

my $self = shift;

$self->{coffee} = 10;

$self->{sugar} = 20;

$self->{warm} = 0;

$self->{warmingtime} = 3;

print __PACKAGE__ . " Init OK.\n";

}

sub CheckWarmUp {

my $self = shift;

print "Checking warmup: ";

if ($self->{warm}) {

print " WARM\n";

}

else {

print " COLD\n";

}

return $self->{warm};

}

sub WarmUp {

my $self = shift;

my $class = ref($self) || $self;

print "Warming Up the $class...";

my $timer = $self->{warmingtime};

while ($timer) {

print $timer--, " ";

sleep(1);

}

print " DONE.\n";

$self->{warm} = 1;

}

sub Identify {

my $self = shift;

my $class = ref($self) || $self;

print "This IS A $class.\n";

}

return 1;

#Tea.pm

#-----------

package Tea;

use strict;

use warnings;

use base qw(CoffeeMachine);

sub new {

my $proto = shift;

my $class = ref($proto) || $proto;

my $self = {

'warmingtime' => 3,

};

bless $self, $class;

$class->SUPER::Identify;

return $self;

}

# nu va fi apelata din

# constructorul de mai sus !

sub Identify {

my $self = shift;

print "This is TEA.\n";

}

return 1;

#Chocolate.pm

#-----------------

package Chocolate;

use strict;

use warnings;

use base qw(CoffeeMachine);

sub new {

my $proto = shift;

my $class = ref($proto) || $proto;

my $self = {

'warmingtime' => 5,

};

bless $self, $class;

$self->Identify;

return $self;

}

return 1;

# automat.pl

# Exemplu de utilizare

# a clasei CoffeeMachine

use strict;

use warnings;

$| = 1;

use lib ".";

use Data::Dumper;

require 'CoffeeMachine.pm';

my $cm = new CoffeeMachine;

if (not $cm->CheckWarmUp) {

$cm->WarmUp();

}

print "\n------------\n";

my @products = ("Tea", "Chocolate");

my @objects;

foreach my $item (@products) {

push @objects, new $item;

}

print Dumper(@objects);

$class->SUPER::Identify este un apel care instruieşte interpretorul să caute în toate clasele moştenite de către clasa copil. În clasa Tea.pm, normal, apelul către Identify ar trebui să fie executat de către metoda locală. Se observă că datorită apelului SUPER, se caută metoda Identify direct în clasele moştenite.

Exerciţiu:

Completaţi clasa CoffeeMachine începută în lucrarea de mai sus (partea II) şi adăugaţi şi alte clase produs.

Documentaţie:

Lista variabilelor speciale: aici

Informaţii detaliate despre obiecte la:

perldoc perlmod

perldoc perlobj

perldoc perlboot

perldoc perltoot