Copyrigtht © 2000 Universita' di Firenze. All rights reserved.
Free license available.
Revisori: Prof. Franco Pirri 
 , Ing. Claudio Bizzarri ![]()
|
Nel corso di questa sezione cercheremo di approfondire maggiormente ognuno di questi
punti sviluppando sempre più nel dettaglio una classe di esempio che chiameremo
Studenti.
Tale classe permetterà di gestire alcuni dati relativi a degli studenti universitari.
Gli attributi della classe saranno costituiti da tutti i dati che siamo interessati
a trattare per ciascuno studente e cioè: matricola, nome, cognome, indirizzo,
telefono e una lista degli esami sostenuti (con i voti riportati).
Inoltre considereremo l'implementazione dei seguenti metodi:
![]() |
Nel caso del nostro esempio memorizzeremo il codice relativo alla classe
Studenti nel file "Studenti.pm" che avrà una struttura del tipo:
#--------------- Studenti.pm ---------------#
package Studenti;
%matricole = ();
# definizione delle funzioni che implementano i metodi della classe
...
#definizione delle (eventuali) funzioni di appoggio
...
1;
In questo modo tutti i programmi che vorranno utilizzare la classe Studenti
la dovranno importare con:
use Studenti;
Notiamo che abbiamo inserito come variabile globale della classe Studenti
la tabella hash %matricole inizializzandola con la lista vuota.
Questa tabella conterrà delle coppie < matricola, reference > che associeranno
a un certo numero di matricola il reference all'oggetto contenente i dati relativi allo
studente con quel numero di matricola.
![]() |
Un coso è una struttura dati anonima
Con il termine coso si intende una struttura dati
(array, hash, struttura composta, ...) anonima, cioè una struttura dati che ha le
seguenti caratteristiche:
$nomi = ['Mario', 'Giulio', 'Francesca'];
$studente = {'matricola' => 1975442, 'nome' => 'Mario', 'cognome' => 'Rossi'};
bless($studente, 'Studenti');
![]() |
Se consideriamo la classe di esempio Studenti il costruttore potrebbe
essere realizzato nel modo seguente:
sub new
{
my $classe = shift; #nome della classe
my ($matricola, $nome, $cognome, $indirizzo, $telefono) = @_;
my $this = {};
$this->{'matricola'} = $matricola;
$this->{'nome'} = $nome;
$this->{'cognome'} = $cognome;
$this->{'indirizzo'} = $indirizzo;
$this->{'telefono'} = $telefono;
$this->{'esami'} = {};
#si registra la nuova coppia <$matricola, $this>
#nella tabella hash %matricole
$matricole{$matricola} = $this;
bless($this, $classe);
return $this;
}
Sulla base di questo esempio possiamo fare le seguenti considerazioni:
my $classe = shift;
my ($matricola, $nome, $cognome, $indirizzo, $telefono) = @_;
my Sthis = {};
$this->{'matricola'} = $matricola;
...
bless($this, $classe);
return $this;
![]() |
Per ora la classe Studenti dispone di un solo metodo: il costruttore. Adesso
svilupperemo ulteriori metodi che ne aumenteranno le funzionalità e ci consentiranno
di vedere attraverso degli esempi le differenze tra metodi di classe, metodi di istanza
e metodi misti.
Metodi di classe
Un tipico esempio di metodo di classe è dato dal
costruttore.
Più in generale sono metodi di classe tutti quei metodi che non operano
su un oggetto particolare o che trattano tutti gli oggetti della classe nel loro
insieme.
Esempi di metodi di classe si possono avere considerando l'implentazione dei metodi
trova() e elimina():
sub trova
{
my ($classe, $matricola) = @_;
return $matricole{$matricola};
}
Il metodo trova() richiede come primo parametro il nome della classe (ed è quindi un
metodo di classe) e come secondo parametro il numero di matricola di uno studente e
ritorna il puntatore all'oggetto contenente i dati dello studente con il numero di
matricola specificato.
sub elimina
{
my ($classe, $matricola) = @_;
delete($matricole{$matricola});
}
Il metodo elimina() accetta come primo parametro il nome della classe e come
secondo parametro il numero di matricola di uno studente ed elimina dalla tabella hash
%matricole la coppia < matricola, reference > corrispondente al
numero di matricola passato alla funzione.
Metodi di istanza
Per quanto riguarda invece i metodi di istanza
possiamo considerare come esempio l'implementazione dei metodi aggiungi_esame(),
media_esami() e visualizza_dati().
sub aggiungi_esame
{
my $this = shift;
my ($esame, $voto) = @_;
$this->{'esami'}{$esame} = $voto;
}
Il metodo aggiungi_esame() richiede come primo parametro il reference ad un
oggetto, come secondo parametro il nome di un esame e come terzo parametro il voto con
cui è stato superato. Il metodo aggiunge l'esame specificato (indicando anche il voto
riportato) alla lista degli esami superati da uno studente.
Come si può vedere, in questo caso, così come faremo negli alti due metodi, si memorizza
il reference all'oggetto nella variabile privata $this in modo da poter avere
accesso all'oggetto stesso.
sub media_esami
{
my $this = shift;
my $somma = 0;
my $num_esami = 0;
my $esami = $this->{'esami'};
foreach $num (values(%$esami))
{
$somma += $num;
$num_esami++;
}
if($num_esami)
{
return ($somma/$num_esami});
}
else
{
return 0;
}
}
Il metodo richiede come unico parametro il reference ad un oggetto e ritorna
la media voto di tutti gli esami sostenuti dallo studente i cui dati sono
contenuti nell'oggetto specificato.
sub visualizza_dati
{
my $this = shift;
my $esami = $this->{'esami'};
print "Nome: $this->{'nome'}\n";
print "Cognome: $this->{'cognome'}\n";
print "Matricola: $this->{'matricola'}\n";
print "Residenza: $this->{'residenza'}\n";
print "Recapito: $this->{'recapito'}\n";
print "Telefono: $this->{'telefono'}\n";
print "Esami sostenuti:\n";
while(($esame, $voto) = each(%$esami))
{
print "$esame , $voto\n";
}
print "\n";
}
Il metodo richiede soltanto che gli venga passato un reference ad un oggetto e stampa
a video i dati dell'oggetto stesso (ovvero i dati di un certo studente).
Metodi misti
Anche se da un punto di vista concettuale la distinzione tra metodi di classe e metodi
di istanza ha un certo senso, dobbiamo comunque osservare che dal punto di vista del
Perl questi due tipi di metodi sono perfettamente analoghi (non si usano costrutti
diversi per realizzarli).
In effeti si possono realizzare metodi che accettano come primo parametro sia
il nome di una classe che il reference ad un oggetto: lo stesso costruttore di una
classe viene spesso realizzato così.
Vediamo come deve essere modificato il costruttore della nostra classe di esempio per
poter operare in questo modo:
sub new
{
my $primo_par = shift;
my $classe = ref($primo_par) || $primo_par; #nome della classe
my ($matricola, $nome, $cognome, $indirizo, $telefono) = @_;
my $this = {};
$this->{'matricola'} = $matricola;
$this->{'nome'} = $nome;
$this->{'cognome'} = $cognome;
$this->{'indirizzo'} = $indirizzo;
$this->{'telefono'} = $telefono;
$this->{'esami'} = {};
#si registra la nuova coppia <$matricola, $this>
#nella tabella hash %matricole
$matricole{$matricola} = $this;
bless($this, $classe);
return $this;
}
In questo caso, rispetto alla versione precedente, si ha che il valore della variabile
$classe viene valutato in base al tipo del primo parametro passato al costruttore:
$primo_par = shift;
$classe = ref($primo_par) || $primo_par;
Il primo passo consiste nel memorizzare il primo parametro passato al costruttore nella
variabile $primo_par e, poi, per determinare il valore della variabile
$classe, si utilizza la funzione ref().
La funzione ref() applicata ad una variabile restituisce il valore falso se
questa non è un reference, altrimenti restituisce il tipo (array, hash) o il nome della
classe a cui appartiene il coso riferito dal reference.
Si può quindi concludere che se $primo_par è un reference allore ref()
ritorna il nome della classe a cui appartiene il coso puntato dal reference, altrimenti
ref() ritorna il valore falso e alla variabile $classe viene assegnato lo
stesso valore di $primo_par in quanto questo non rappresenta un reference ma un
nome di classe.
![]() |
Modo tradizionale
Come sappiamo un metodo non è altro che una funzione ed è quindi logico che come tale
possa anche essere invocato:
#crea un nuovo oggetto
$oggetto = Studenti::new('Studenti', 1984242, 'Mario', 'Rossi',
'Via Verdi n.4', '055-242526');
#aggiunge un nuovo esame alla lista di quelli sostenuti
Studenti::aggiungi_esame($oggetto, 'Fisica 1', 26);
Utilizzando questo tipo di invocazione dobbiamo ricordarci di utilizzare i nomi estesi
delle funzioni invocate e di passare sempre come primo parametro il nome di una classe
o il refernce ad un oggetto.
Questo modo di invocare un metodo è comunque sconsigliato perchè non consente di
sfrutttare il meccanismo dell'ereditarietà.
Modo ad oggetto indiretto
Le due precedenti invocazioni assumono adesso la seguente forma:
$oggetto = new Studenti (1984242, 'Mario', 'Rossi',
'Via Verdi n.4', '055-242526');
aggiungi_esame $oggetto ('Fisica 1', 26);
Questo tipo di invocazione consente di determinare il secondo termine (cioè il nome
della classe o il reference all'oggetto) tramite la valutazione di un blocco di
istruzioni.
Modo object-oriented
Fra i tre metodi di invocazione è quello più utilizzato:
$oggetto = Studenti->new(1984242, 'Mario', 'Rossi',
'Via Verdi n.4', '055-242526');
$oggetto->aggiungi_esame('Fisica 1', 26);
Utilizzando questo tipo di invocazione si possono anche effettuare delle chiamate in
cascata.
Osservazioni
Dagli esempi sopra riportati si può notare che l'utilizzo di una classe risulta più
comodo di quello di un modulo generico.
In questo caso infatti non ci si deve preoccupare di esportare o importare simboli
(tramite l'uso del modulo Exporter) in quanto, utilizzando uno qualsiasi dei tre modi
di invocazione di un metodo, il nome del package a cui si fa riferimento è sempre
disponibile.
Ciò appare evidente per i metodi di classe, ma risulta vero anche per i metodi di
istanza in quanto questi, richiedendo come primo parametro il reference ad un oggetto,
hanno sempre a disposizione (anche se implicitamente) il nome della classe in cui il
metodo è stato definito.
![]() |
Nella sezione dedicata ai fondamenti della programmazione orientata agli oggetti
abbiamo parlato di incapsulazione e abbiamo detto
che l'utente non deve essere a conoscenza della struttura di una classe e dei suoi
dettagli realizzativi.
In pratica un utente non può manipolare direttamente i valori degli attributi
di un oggetto, ma deve utilizzare dei metodi specifici.
Affinchè ciò sia possibile colui che sviluppa la classe deve realizzare un metodo
per ognuno degli attributi di cui si vuole conoscere o modificare il valore.
In classi non banali tale compito può richiedere di dover implementare un numero molto
elevato di metodi: le cose possono essere semplificate se si ricorre
all'utilizzo della funzione AUTOLOAD.
In pratica si suppone che, per ogni attributo di cui si vuole leggere o modificare
il valore, esista un metodo corrispondente (che abbia cioè lo stesso nome
dell'attributo).
Ogni volta che l'utente invoca uno di questi metodi inesistenti il controllo viene
passato alla funzione AUTOLOAD e questa provvede a gestire l'accesso agli attributi di
un oggetto in modo del tutto trasparente.
Consideriamo di nuovo la nostra classe di esempio e supponiamo di voler dare accesso
in sola lettura all'attributo 'matricola' e in lettura/scrittura a tutti gli altri
attributi (ad esclusione di 'esami').
Questo risultato può essere ottenuto realizzando la seguente funzione AUTOLOAD:
sub AUTOLOAD
{
my $this = shift;
my $attributo = $AUTOLOAD;
#Elimina l'eventuale intestazione presente nel nome esteso.
#Garantisce il corretto funzionamento nel caso in cui il
#metodo sia stato invocato in modo tradizionale
$attributo = ~s/.*:://;
#Se il nome del metodo non coincide con il nome di un attributo
#oppure coincide con l'attributo 'esami' allora il programma
#termina
unless((exists $this->{$attributo}) || ($attributo eq 'esami'))
{
die "Il metodo $attributo non esiste";
}
#Se la funzione ha un secondo parametro allora significa che si
#vuole effettuare un'assegnazione: l'assegnazione viene effettuata
#a meno che non si cerchi di modificare il valore dell'attributo
#'matricola'. Se al contrario la funzione non ha un secondo parametro
#significa che si deve restituire il valore dell'attributo.
if(@_)
{
if($attributo eq 'matricola')
{
die "Impossibile modificare l'attributo $attributo";
}
else
{
return $this->{$attributo} = shift;
}
else
{
return $this->{$attributo}
}
}
I metodi (fittizi) per l'accesso agli attributi di un oggetto si utilizzano come un
qualsiasi altro metodo:
$oggetto = Studenti->new(1984242, 'Mario', 'Rossi',
'Via Verdi n. 4', '055-242526');
...
#modifica l'indirizzo
$oggetto->indirizzo('Via Neri n.5');
#modifica il numero di telefono
$oggetto->telefono('055-232425');
#stampa il numero di matricola
print "$oggetto->matricola()\n";
![]() |
Ogni linguaggio di programmazione risolve a suo modo il problema della garbage collection.
Nel caso del C++, ad esempio, la rimozione delle aree di memoria inutilizzate è
lasciata completamente a carico del programmatore.
Al contrario Java implementa uno dei garbage collector
(raccoglitore di spazzatura) più
completi ed automatizzati.
Il Perl, dal canto suo, implementa invece un garbage collector
molto semplice che si basa sul meccanismo del
conteggio dei riferimenti.
Quando creiamo una struttura dati questa viene memorizzata in una certa area di
memoria e può essere riferita da un certo numero di reference (puntatori).
Supponiamo ad esempio di scrivere la seguente riga di codice:
$nominativo = 'Mario Rossi';
in questo modo si crea una nuova variabile scalare che contiene il valore 'Mario Rossi'.
Se analizziamo più nel dettaglio il nome della variabile $nominativo possiamo
osservare che questo è costituito da due parti: la prima è data dal simolo $ ed
indica il tipo della variabile (in questo caso si tratta di uno scalare); la seconda è
il nome vero e proprio della variabile cioè nominativo e, in pratica, rappresenta
un reference (o puntatore) all'area di memoria in cui è contenuto il valore 'Mario
Rossi'.
La precedente istruzione crea quindi una nuova variabile per la quale esiste un solo
riferimento.
Se invece scriviamo qualcosa del tipo:
$nominativo = 'Mario Rossi';
$ref = \$nominativo;
in pratica creiamo una nuova variabile e un reference ad essa: in questo caso esistono
due riferimenti all'area di memoria contenente il valore 'Mario Rossi'.
Per effettuare la garbage collection il Perl associa
ad ogni area di memoria un contatore il cui valore indica il numero di riferimenti a
quell'area di memoria che sono attivi ad un certo istante di tempo.
Tutte le volte che una variabile esce dal suo ambito di validità il
garbage collector del Perl entra in azione e controlla il contatore associato
all'area di memoria riferita dalla variabile: