Telemat Lab's home page


Copyrigtht © 2000 Universita' di Firenze. All rights reserved.

Free license available.

 

Moduli e Oggetti in Perl

Classi ed Oggetti in Perl

A cura di: David Cristofani

Revisori: Prof. Franco Pirri  , Ing. Claudio Bizzarri 


Classi ed Oggetti in Perl

HYPER HOME Indice gen. Indice prec. Indice attuale Indice succ.

 

Indice generale
Indice lezione attuale
Slide iniziale gruppo precedente
Slide succesiva
Testo
slide 1
Slide 1/8

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:

Slide successiva Slide precedente

 

 

 

 

Indice generale
Indice lezione attuale
Slide iniziale
Slide precedente
Slide successiva
Testo
slide 2
Slide 2/8

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.

Slide successiva Slide precedente

 

 

 

 

Indice generale
Indice lezione attuale
Slide iniziale
Slide precedente
Slide successiva
Testo
slide 3
Slide 3/8

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:

Se consideriamo ad esempio:

$nomi = ['Mario', 'Giulio', 'Francesca'];
$studente = {'matricola' => 1975442, 'nome' => 'Mario', 'cognome' => 'Rossi'};

si ha che $nomi e $studente sono due reference, mentre l'array anonimo ['Mario', 'Giulio', 'Francesca'] e l'hash anonimo {'matricola' => 1975442, 'nome' => 'Mario', 'cognome' => 'Rossi'} sono due cosi nel senso che abbiamo appena spiegato.


La funzione bless()

Affinchè un coso conosca la classe a cui appartiene e quindi diventi un coso benedetto (ovvero un oggetto) è necessario "benedirlo" attraverso la funzione bless().
La funzione bless() può essere chiamata con due parametri: il primo deve essere il reference ad un coso e il secondo deve essere il nome di una classe (ovvero il nome di un package).

Se consideriamo la classe Studenti, dopo l'esecuzione della seguente istruzione:

bless($studente, 'Studenti');

il coso riferito da $studente saprà di appartenere alla classe Studenti e quindi diventerà un coso benedetto.

In realtà la funzione bless() può anche essere richiamata fornendo come unico parametro il reference ad un coso: in questo caso viene assunto che il nome della classe sia quello del package all'interno del quale la funzione è stata richiamata.
Ad ogni modo, per motivi legati al meccanismo di funzionamento dell'ereditarietà in Perl, è sempre bene specificare esplicitamente anche il nome della classe.

Slide successiva Slide precedente

 

 

 

 

Indice generale
Indice lezione attuale
Slide iniziale
Slide precedente
Slide successiva
Testo
slide 4
Slide 4/8

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: Osservazioni

L'utilizzo di $this per riferirsi al coso all'interno del costruttore è assolutamente arbitrario (è una convenzione utile per chi è abituato alla programmazione in C++).

Dobbiamo inoltre notare che in Perl non esistono costrutti specifici per l'implementazione degli attributi.
Nell'esempio abbiamo utilizzato un hash, ma nessuno avrebbe potuto vietare di utilizzare un array o un'altra struttura: la scelta su come implementare gli attributi di una classe è lasciata al programmatore.

Slide successiva Slide precedente

 

 

 

 

Indice generale
Indice lezione attuale
Slide iniziale
Slide precedente
Slide successiva
Testo
slide 5
Slide 5/8

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.

Slide successiva Slide precedente

 

 

 

 

Indice generale
Indice lezione attuale
Slide iniziale
Slide precedente
Slide successiva
Testo
slide 6
Slide 6/8

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.

Slide successiva Slide precedente

 

 

 

 

Indice generale
Indice lezione attuale
Slide iniziale
Slide precedente
Slide successiva
Testo
slide 7
Slide 7/8

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";

Slide successiva Slide precedente

 

 

 

 

Indice generale
Indice lezione attuale
Prima slide sezione successiva
Slide iniziale
Slide precedente
Testo
slide 8
Slide 8/8

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: Ovviamente quanto detto risulta valido anche per gli oggetti poichè essi, come tutte le altre strutture dati, occupano una certa area di memoria.
Per questo motivo, quando l'ultimo refernce ad un oggetto esce dal suo ambito di validità, l'oggetto viene automaticamente distrutto (cioè viene liberata l'area di memoria da esso occupata).

Prima slide gruppo successivo Slide precedente


Ultimo aggiornamento 5/12/2000.

Slide iniziale


Telemat Lab's home page

HYPER HOME Indice generale Indice prec. Indice attuale Indice succ.