NOME

perlboot - Introduzione alla tecnologia Orientata agli Oggetti (titolo originale: Beginner's Object-Oriented Tutorial)

DESCRIZIONE

Se non avete già una certa familiarità con la tecnologia ad oggetti degli altri linguaggi di programmazione, parte della documentazione sulla OOP in Perl potrebbe essere un po' intimidatoria: perlobj, una guida di riferimento sull'utilizzo degli oggetti e perltoot che introduce il lettore alle particolarità della tecnologia ad oggeti del Perl con un taglio introduttivo.

Partiamo dunque con un approccio differente, non assumendo a priori alcuna esperienza con gli oggetti. Può aiutare la conoscenza delle subroutine (perlsub), dei riferimenti (perlref e seguenti), e dei package (perlmod), dunque se non la possedete già, conviene che vi familiarizzate con questi argomenti.

Se potessimo parlare con gli animali...

Per un attimo, lasciamo parlare gli animali:

sub Mucca::parla {
  print "la Mucca fa muuuu!\n";
}
sub Cavallo::parla {
  print "il Cavallo fa hiiii!\n";
}
sub Pecora::parla {
  print "la Pecora fa beee!\n"
}

Mucca::parla;
Cavallo::parla;
Pecora::parla;

Questo produce:

la Mucca fa muuuu!
il Cavallo fa hiiii!
la Pecora fa beee!

Fino a qui niente di spettacolare. Semplici subroutine, anche se da package separati e chiamate usando il nome completo del package. Creiamo dunque un intero pascolo:

# Mucca::parla, Cavallo::parla, Pecora::parla come prima
@pascolo = qw(Mucca Mucca Cavallo Pecora Pecora);
foreach $animale (@pascolo) {
  &{$animale."::parla"};
}

Questo produce:

la Mucca fa muuuu!
la Mucca fa muuuu!
il Cavallo fa hiiii!
la Pecora fa beee!
la Pecora fa beee!

Urca. Questa dereferenziazione di un riferimento simbolico a codice è piuttosto brutta. Ci stiamo affidando al comportamento dato da no strict subs, che non è certamente raccomandato per programmi di una certa dimensione. E perché era necessario? Perché il nome del package sembra essere inseparabile dal nome della subroutine che vogliamo invocare all'interno di quel package.

O no?

Presentiamo la freccia per invocare i metodi

Per ora, diciamo che Classe->metodo invoca la subroutine metodo nel package Classe. (In questo contesto, "Classe" è usata nel suo significato "categorico", non nel suo significato "scolastico"). Questo non è del tutto preciso, ma lo rivedremo a tempo debito. Per ora, utilizziamolo in questa maniera:

# Mucca::parla, Cavallo::parla, Pecora::parla come prima
Mucca->parla;
Cavallo->parla;
Pecora->parla;

Ancora una volta, questo produce:

la Mucca fa muuuu!
il Cavallo fa hiiii!
la Pecora fa beee!

Questo non è ancora divertente. Stesso numero di caratteri, solo costanti, nessuna variabile. Malgrado tutto, le parti ora sono separabili. Osservate:

$a = "Mucca";
$a->parla; # invoca Mucca->parla

Ahh! Ora che il nome del package è stato separato dal nome della subroutine, possiamo usare un nome di package variabile. E questa volta abbiamo qualcosa che funziona anche quando use strict refs è in funzione.

Invochiamo un cortile

Prendiamo questa nuova invocazione a freccia e utilizziamola nell'esempio del cortile:

sub Mucca::parla {
  print "la Mucca fa muuuu!\n";
}
sub Cavallo::parla {
  print "il Cavallo fa hiiii!\n";
}
sub Pecora::parla {
  print "la Pecora fa beee!\n"
}

@pascolo = qw(Mucca Mucca Cavallo Pecora Pecora);
foreach $animale (@pascolo) {
  $animale->parla;
}

Ecco! Ora abbiamo tutti gli animali che parlano, e in modo sicuro, senza l'uso di riferimenti simbolici a codice.

Ma guardate tutto quel codice in comune. Ognuna delle routine parla ha infatti una struttura similare: un operatore print ed una stringa che contiene testo identico tranne che per due parole. Sarebbe carino poter raccogliere a fattor comune le similitudini, nel caso si decida in seguito di mettere "dice" al posto di "fa".

E in effetti abbiamo un sistema per farlo senza troppo sforzo, anche se dobbiamo imparare qualcosa in più su cosa faccia davvero la freccia per invocare i metodi.

I parametri addizionali dell'invocazione di metodo

L'invocazione di:

Classe->metodo(@argomenti)

tenta di invocare la subroutine Classe::metodo come se avessimo scritto:

Classe::metodo("Classe", @argomenti);

(Se la subroutine non può essere trovata, l'"ereditarietà" entra in funzione, ma ci arriveremo più avanti). Questo significa che riceviamo il nome della classe come primo parametro (il solo parametro, se non viene fornito alcun argomento). Possiamo dunque riscrivere il parlare della Pecora come:

sub Pecora::parla {
  my $classe = shift;
  print "una $classe fa beeeeh!\n";
}

Gli altri due animali risultano simili:

sub Mucca::parla {
  my $classe = shift;
  print "una $classe fa muuuu!\n";
}
sub Cavallo::parla {
  my $classe = shift;
  print "un $classe fa hiiii!\n";
}

In ogni caso, $classe prenderà il valore appropriato per quella subroutine. Ma di nuovo, abbiamo un mucchio di struttura simile. Possiamo raccogliere a fattor comune quello che ancora resta? Sì, chiamando un ulteriore metodo nella stessa classe.

Chiamare un secondo metodo per semplificare le cose

Richiamiamo da parla un metodo di supporto che chiamiamo suono. Questo metodo fornisce una costante di testo per il suono stesso.

    { package Mucca;
      sub suono { "muuu" }
      sub parla {
	my $classe = shift;
	print "una $classe fa ", $classe->suono, "!\n"
      }
    }

Ora, quando chiamiamo Mucca->parla, riceviamo una $classe Mucca in parla. Questo a sua volta seleziona il metodo Mucca->suono che restituisce "muuuu". Ma questo, quanto sarà differente per il Cavallo?

    { package Cavallo;
      sub suono { "hiiii" }
      sub parla {
	my $classe = shift;
	print "un $classe fa ", $classe->suono, "!\n"
      }
    }

Cambiano solo il nome del package, l'articolo e il suono. Allora in quale maniera possiamo condividere la definizione di parla tra la Mucca e il Cavallo? Con l'ereditarietà!

Ereditare le trachee

Definiamo un package di subroutine comuni chiamato Animale, con la definizione per parla:

    { package Animale;
      sub parla {
	my $classe = shift;
	print $classe->articolo," $classe fa ", $classe->suono, "!\n"
      }
    }

Poi, per ogni animale, diciamo che esso "eredita" da Animale, e definiamo lo specifico suono e l'articolo per quell'animale:

{ package Mucca;
  @ISA = qw(Animale);
  sub suono { "muuuu" }
  sub articolo { "una" }
}

Da notare l'aggiunta dell'array @ISA. Ci arriveremo tra un minuto.

Ma cosa succede quando ora invochiamo Mucca->parla?

Per prima cosa, Perl costruisce la lista degli argomenti. In questo caso c'è solo Mucca. Poi Perl cerca Mucca::parla. Ma qui non c'è, così Perl controlla l'array di ereditarietà @Mucca::ISA. Questo c'è e contiene il solo nome Animale.

Quindi, Perl controlla parla all'interno di Animale, ovvero Animale::parla. E questo c'è, per cui Perl invoca la subroutine con la lista di argomenti fissata precedentemente.

All'interno della subroutine Animale::parla, $classe diventa Mucca (che è il primo argomento). Così quando arriva il momento di invocare $classe->suono, si cercherà Mucca->suono, che si ottiene al primo tentativo senza esaminare @ISA. Vittoria!

Qualche nota su @ISA

Questa magica variabile chiamata @ISA (da "is a" [è un/una, NdT]), ha dichiarato che Mucca "è un" Animale. Da notare che questa è un array, non un singolo valore, visto che in rare occasioni ha senso cercare i metodi mancanti in più di una classe madre.

Se anche Animale ha una sua @ISA, allora si cercherà anche lì. La ricerca è ricorsiva, in profondità [depth-first, NdT] e da sinistra a destra per ciascun @ISA. Di solito, ogni @ISA possiede un solo elemento (avere più di un elemento significa ereditarietà multipla e multipli mal di testa), dunque abbiamo un simpatico albero di ereditarietà.

Quando utilizziamo use strict, avremo delle lamentele su @ISA da parte sua, dato che non è una variabile contenente un nome di package esplicito, né è una variabile lessicale (dichiarata con my). Non possiamo comunque farne una variabile lessicale (deve appartenere al package per essere trovata dal meccanismo di ereditarietà), ma il problema si può risolvere in maniera comoda, in un paio di modi.

La maniera più semplice è quella di specificare il nome del package:

@Mucca::ISA = qw(Animale);

Altrimenti possiamo dichiararla in modo da tenere il nome del package implicito:

package Mucca;
use vars qw(@ISA);
@ISA = qw(Animale);

Se si sta caricando la classe dall'esterno, attraverso un modulo orientato agli oggetti, si può sostituire:

package Mucca;
use Animale;
use vars qw(@ISA);
@ISA = qw(Animale);

con:

package Mucca;
use base qw(Animale);

E questo è proprio compatto.

Ridefinire i metodi

Aggiungiamo un Topo, che si sente appena:

    # Il package Animale e` come prima
    { package Topo;
      @ISA = qw(Animale);
      sub suono { "squit" }
      sub articolo { "un" }
      sub parla {
        my $classe = shift;
	print $classe->articolo," $classe fa ", $classe->suono, "!\n";
	print "[ma si sente appena!]\n";
      }
    }

    Topo->parla;

che produce:

un Topo fa squit!
[ma si sente appena!]

In questo caso, Topo ha la sua personale routine per parlare, dunque Topo->parla non invoca immediatamente Animale->parla. Tutto ciò prende il nome di "ridefinizione" ["override" in Inglese, NdT]. In effetti, non avevamo neppure bisogno di affermare che un Topo sia un Animale, dato che tutti i metodi che sono necessari a parla sono completamente definiti in Topo.

Ma ora abbiamo duplicato parte del codice di Animale->parla, e questo può portare ancora una volta ad un mal di testa per la manutenzione del codice. Come possiamo evitarlo? Possiamo dire in qualche modo che un Topo fa tutto quello che fa ogni altro Animale, ma aggiungendo il commento in più? Sicuro!

Per prima cosa, possiamo invocare direttamente il metodo Animale::parla:

    # Il package Animale e` come prima
    { package Topo;
      @ISA = qw(Animale);
      sub suono { "squit" }
      sub articolo { "un" }
      sub parla {
        my $classe = shift;
        Animale::parla($classe);
	print "[ma si sente appena!]\n";
      }
    }

Notate che ora dobbiamo includere il parametro $classe (che quasi certamente ha il valore "Topo") come primo parametro per Animale::parla, dal momento che non usiamo più la freccia. Perché non la usiamo? Vediamo, se qui invochiamo Animale->parla, il primo parametro per il metodo sarà "Animale" e non "Topo", e quando sarà il momento di chiamare il suono, non si avrà la giusta classe per ritornare a questo package.

Resta il fatto che invocare direttamente Animale::parla è un pasticcio. Cosa succederebbe se Animale::parla non esistesse, ma fosse ereditato da una classe menzionata in @Animale::ISA? Visto che non stiamo più usando l'invocazione a freccia, abbiamo una ed una sola possibilità di trovare la giusta subroutine.

C'è anche da notare che il nome della classe Animale è ora cablato nella subroutine. Questo è un pasticcio se qualcuno fa manutenzione del codice, cambiando @ISA per Topo senza accorgersi di quel Animale in parla. Quindi è probabile che questa non sia la maniera corretta di agire.

Iniziare la ricerca da un posto diverso

Una soluzione migliore è quella di dire a Perl di cominciare a cercare da un punto più in alto nella catena di ereditarietà:

    # Animale e` come prima
    { package Topo;
      # stessi @ISA, &suono e &articolo di prima
      sub parla {
        my $classe = shift;
        $classe->Animale::parla;
	print "[ma si sente appena!]\n";
      }
    }

Ahh. Questo funziona. Utilizzando questa sintassi, partiamo con Animale per trovare parla, ed utilizziamo tutta la catena di ereditarietà di Animale se non la trovassimo immediatamente. Inoltre, il primo parametro sarà $classe, dunque il metodo parla trovato avrà Topo come suo primo parametro, e riuscirà a trovare Topo::suono per i dettagli.

Ma questa non è la soluzione ottima. Dobbiamo ancora mantenere coordinati @ISA e il package da cui iniziare la ricerca. E peggio ancora, se Topo avesse più elementi in @ISA, potremmo non sapere quale di questi ha effettivamente definito parla. Ma allora, c'è una soluzione ancora migliore?

Il modo SUPER di fare le cose

Cambiando la classe Animale nella classe SUPER in quella invocazione, abbiamo una ricerca automatica di tutte le nostre classi madri presenti in @ISA:

    # Animale e` come prima
    { package Topo;
      # stessi @ISA, &suono e &articolo di prima
      sub parla {
        my $classe = shift;
        $classe->SUPER::parla;
	print "[ma si sente appena!]\n";
      }
    }

Dunque, SUPER::parla significa cercare parla nell'@ISA del package corrente, invocando il primo trovato. Va notato che non guarda nell'@ISA di $classe.

A che punto siamo arrivati...

Fino a qui, abbiamo visto la sintassi a freccia per i metodi:

Classe->metodo(@argomenti);

o in maniera analoga:

$a = "Classe";
$a->metodo(@argomenti);

che costruisce una lista di argomenti formata da:

("Classe", @argomenti)

e cerca di invocare

Classe::metodo("Classe", @Argomenti);

Ad ogni modo, se Classe::metodo non viene trovato, allora @Classe::ISA viene esaminato (ricorsivamente) per individuare un package che effettivamente contenga metodo, e quella subrotuine viene invocata al suo posto.

Utilizzando questa semplice sintassi, abbiamo i metodi di classe, l'ereditarietà (multipla), la ridefinizione e l'estendibilità. Usando quello che abbiamo visto fino a qui, siamo stati in grado di rendere a fattor comune il codice simile e di fornire una maniera carina per riutilizzare le implementazioni con delle variazioni. Questo fa parte del nucleo di quello che possono offrirci gli oggetti, ma essi ci forniscono anche i dati di istanza, che non abbiamo ancora iniziato a vedere.

Un cavallo è un cavallo, senza inganno senza inganno -- o no?

Partiamo con il codice per la classe Animale e per la classe Cavallo:

{ package Animale;
  sub parla {
    my $classe = shift;
    print $classe->articolo," $classe fa ", $classe->suono, "!\n"
  }
}
{ package Cavallo;
  @ISA = qw(Animale);
  sub suono { "hiiii!" }
  sub articolo { "un" }
}

Questo ci permette di invocare Cavallo->parla, che risale fino a Animale::parla, riscende per chiamare Cavallo::suono e Cavallo::articolo per ottenere il suono e l'articolo giusto, ottenendo infine l'output seguente:

un Cavallo fa hiiii!

Ma tutti i nostri oggetti Cavallo devono essere assolutamente identici. Se viene aggiunta una subroutine, tutti i cavalli automaticamente la condividono. Questo è ottimo se vogliamo che tutti i cavalli siano lo stesso, ma come catturiamo la distinzione tra un singolo cavallo ed un altro? Per esempio supponiamo che io voglia dare un nome al mio primo cavallo. Ci deve essere un modo per mantenere il suo nome separato dagli altri cavalli.

Possiamo fare questo tracciando una nuova distinzione, chiamata una "istanza". Una "istanza" di solito è creata da una classe. In Perl, ogni riferimento può essere un'istanza, dunque partiamo con il riferimento più semplice che possa contenere il nome di un cavallo: un riferimento ad uno scalare.

my $nome = "Sig. Ed";
my $parlante = \$nome;

Perciò ora $parlante è un riferimento a quello che sarà il dato specifico dell'istanza (il nome). Il passo finale per renderlo una vera istanza consiste nell'usare lo speciale operatore chiamato bless:

bless $parlante, Cavallo;

Questo operatore immagazzina l'informazione sul package chiamato Cavallo nella cosa puntata dal riferimento. A questo punto, diciamo che $parlante è una istanza di Cavallo. Ovvero, è uno specifico cavallo. Nessun altro aspetto del riferimento è cambiato, e può ancora essere usato con i tradizionali operatori di derefenziazione.

Invocare un metodo di istanza

L'invocazione a freccia può essere usata con le istanze, così come con i nomi dei package (classi). Dunque prendiamo il suono che produce $parlante:

my $rumore = $parlante->suono;

Per invocare suono, il Perl per prima cosa si accorge che $parlante è un riferimento blessed (e quindi una istanza). Poi costruisce una lista di argomenti, in questo caso formata dal solo $parlante. (Più avanti vedremo che gli argomenti si posizioneranno di seguito alla variabile istanza, proprio come con le classi).

Adesso viene la parte divertente: Perl prende la classe nella quale l'istanza è stata blessed, in questo caso Cavallo, e la usa per individuare la subroutine per invocare il metodo. In questo caso, Cavallo::suono viene trovato direttamente (senza l'uso dell'ereditarietà), producendo l'invocazione finale della subroutine:

Cavallo::suono($parlante)

Da notare che in questo caso il primo parametro è ancora l'istanza e non il nome della classe come era prima. Avremo "hiiii" come valore di ritorno, che finirà nella variable $rumore.

Se Cavallo::suono non fosse stato trovato, si sarebbe scorsa tutta la lista di elementi di @Cavallo::ISA per cercare di trovare il metodo in una delle superclassi, analogamente a un metodo di classe. La sola differenza tra un metodo di classe e un metodo di istanza sta nel primo parametro, che può essere un'istanza (un riferimento blessed) o un nome di classe (una stringa).

Accesso ai dati dell'istanza

Dato che riceviamo l'istanza come primo parametro, possiamo ora accedere ai suoi dati specifici. In questo caso, andiamo ad aggiungere una maniera per ottenere il nome:

{ package Cavallo;
  @ISA = qw(Animale);
  sub suono { "hiiii" }
  sub articolo { "un" }
  sub nome {
    my $self = shift;
    $$self;
  }
}

Ora chiamiamo per ottenere il nome:

print $parlante->nome, " dice ", $parlante->suono, "\n";

All'interno di Cavallo::nome, l'array @_ contiene solo $parlante, che shift scrive in $self. (È consuetudine trasferire il primo parametro in una variabile chiamata $self per i metodi di istanza, perciò fate altrettanto a meno che non abbiate ottime ragioni). Alla riga successiva, $self viene dereferenziata come un riferimento a scalare, producendo Sig. Ed, e con questo siamo a posto. Il risultato è:

Sig. Ed dice hiiii.

Come costruire un cavallo

Naturalmente, se costruiamo tutti i nostri cavalli a mano, molto probabilmente faremo degli errori di tanto in tanto. Inoltre, stiamo violando una delle proprietà della programmazione orientata agli oggetti, in quanto le "interiora" di un Cavallo sono visibili. Questo è positivo se voi siete dei veterinari, ma non se vi interessa solo possedere un cavallo. Lasciamo allora che la classe Cavallo costruisca un nuovo cavallo:

{ package Cavallo;
  @ISA = qw(Animale);
  sub suono { "hiiii" }
  sub articolo { "un" }
  sub nome {
    my $self = shift;
    $$self;
  }
  sub chiamato {
    my $classe = shift;
    my $nome = shift;
    bless \$nome, $classe;
  }
}

Ora con il nuovo metodo chiamato, possiamo costruire un cavallo:

my $parlante = Cavallo->chiamato("Sig. Ed");

C'è da notare che siamo tornati ad un metodo di classe, dunque i due argomenti di Cavallo::chiamato sono Cavallo e "Sig. Ed". L'operatore bless non solo fa un bless su $nome, ma restituisce anche il riferimento a $nome, che è comodo come valore di ritorno. E questo è come si costruisce un cavallo.

In questo caso, abbiamo chiamato il costruttore chiamato in maniera che indichi chiaramente che l'argomento del costruttore è il nome per questo particolare Cavallo. Si possono usare costruttori differenti con nomi differenti per differenti modi di "dare vita" all'oggetto (come ad esempio registrare il suo pedigree o la data di nascita). Comunque sia, noterete che molte persone che arrivano al Perl proveniendo da linguaggi più limitati, utilizzano un unico costruttore chiamato new, interpretando in svariati modi gli argomenti di new. Ciascuno dei due stili è adeguato, purché si documenti la propria particolare maniera di dare vita ad un oggetto. (E stavate già progettando di farlo, vero?)

Ereditare il costruttore

C'era qualcosa di specifico di Cavallo in quel metodo? No. Quindi è anche la stessa ricetta per costruire qualsiasi cosa che eredita da Animale, dunque scriviamolo al suo posto:

{ package Animale;
  sub parla {
    my $classe = shift;
    print $classe->articolo," $classe fa ", $classe->suono, "!\n"
  }
  sub nome {
    my $self = shift;
    $$self;
  }
  sub chiamato {
    my $classe = shift;
    my $nome = shift;
    bless \$nome, $classe;
  }
}
{ package Cavallo;
  @ISA = qw(Animale);
  sub suono { "hiiii" }
  sub articolo { "un" }
}

Ahh, ma cosa succede se invochiamo parla su un'istanza?

my $parlante = Cavallo->chiamato("Sig. Ed");
$parlante->parla;

Ci ritroviamo con un valore di debug:

un Cavallo=SCALAR(0xaca42ac) dice hiiii!

Perché? Perché la routine Animale::parla si aspetta un nome di classe come suo primo parametro, non una istanza. Quando l'istanza viene passata, finiamo per usare un riferimento blessed a scalare come se fosse una stringa, e questo produce quello che abbiamo appena visto.

Fare un metodo che funzioni sia con le classi che con le istanze

Ci serve che il metodo possa distinguere se è stato chiamato su una classe o su un'istanza. La maniera più diretta è tramite l'operatore ref. Questo restituisce una stringa (il nome della classe) quando viene usato su di un riferimento blessed, e undef quando viene usato su di una stringa (come un nome di una classe). Andiamo a modificare il metodo nome per notare la differenza:

sub nome {
  my $cosa = shift;
  ref $cosa
    ? $$cosa # e` un'istanza, restituisco il nome
    : $cosa->articolo()." $cosa senza nome"; # e` una classe
}

Qui, l'operatore ?: è comodo per selezionare o la dereferenziazione o la stringa derivata. Ora possiamo usare il metodo sia per un'istanza sia per una classe. Da notare che ho cambiato il nome del primo parametro in $cosa per sottolineare che vogliamo sapere cosa è stato passato:

my $parlante = Cavallo->chiamato("Sig. Ed.");
print Cavallo->nome, "\n"; # stampa "un Cavallo senza nome\n"
print $parlante->nome, "\n"; # stampa "Sig. Ed\n"

Ora correggiamo parla per sfruttarlo:

sub parla {
  my $cosa = shift;
  print $cosa->nome, " fa ", $cosa->suono, "\n";
}

Dato che suono funziona già sia con una classe che con un'istanza, siamo a posto!

Aggiungere parametri ad un metodo

Addestriamo i nostri animali a mangiare:

{ package Animale;
  sub chiamato {
    my $classe = shift;
    my $nome = shift;
    bless \$nome, $classe;
  }
  sub nome {
    my $cosa = shift;
    ref $cosa
      ? $$cosa # e` un'istanza, restituisco il nome
      : $cosa->articolo()." $cosa senza nome"; # e` una classe
  }
  sub parla {
    my $cosa = shift;
    print $cosa->nome, " fa ", $cosa->suono, "\n";
  }
  sub mangia {
    my $cosa = shift;
    my $cibo = shift;
    print $cosa->nome, " mangia $cibo.\n";
  }
}
{ package Cavallo;
  @ISA = qw(Animale);
  sub suono { "hiiii" }
  sub articolo { "un" }
}
{ package Pecora;
  @ISA = qw(Animale);
  sub suono { "beeee" }
  sub articolo { "una" }
}

Ora proviamoli:

my $parlante = Cavallo->chiamato("Sig. Ed");
$parlante->mangia("fieno");
Pecora->mangia("erba");

che stampa:

Sig. Ed mangia fieno.
una Pecora senza nome mangia erba.

Un metodo di istanza con parametri viene invocato con l'istanza seguita dell'elenco dei parametri. Quindi la prima invocazione è come se fosse:

Animale::mangia($parlante, "fieno");

Istanze più interessanti

Cosa succede se un'istanza ha bisogno di più dati? Le istanze più interessanti sono fatte da molte voci, ognuna delle quali può essere a sua volta un riferimento o anche un altro oggetto. La maniera più semplice per memorizzarli è spesso all'interno di un hash. Le chiavi dell'hash vengono utilizzate come i nomi delle parti dell'oggetto (spesso chiamate "variabili di istanza" o "variabili membro"), e i corrispondenti valori sono, beh, i valori.

Ma come facciamo stare un cavallo in un hash? Ricordiamoci che un oggetto era un qualsiasi riferimento blessed. Possiamo usare senza problemi un riferimento ad hash invece che un riferimento a scalare, purché tutto ciò che esamina il rifemento venga modificato in conseguenza.

Facciamo una pecora che abbia un nome ed un colore:

my $cattiva = bless { Nome => "Malvagia", Colore => "nero" }, Pecora;

dunque $cattiva->{Nome} è Malvagia, e $cattiva->{Colore} è nero. Ma noi vogliamo che $cattiva->nome acceda al nome e fare questo ora non funziona più dato che si aspetta un riferimento ad uno scalare. Non c'è da preoccuparsi, visto che è davvero facile correggerlo:

## in Animale
sub nome {
  my $cosa = shift;
  ref $cosa ?
    $cosa->{Nome} :
    $cosa->articolo()." $cosa senza nome";
}

Naturalmente chiamato costruisce ancora una pecora scalare, dunque correggiamo pure quello:

## in Animale
sub chiamato {
  my $classe = shift;
  my $nome = shift;
  my $self = { Nome => $nome, Colore => $classe->colore_di_default };
  bless $self, $classe;
}

Cos'è questo colore_di_default? Beh, se chiamato riceve solo il nome, dobbiamo definire un colore, dunque avremo uno specifico colore iniziale per la classe. Per una pecora, possiamo definirlo come bianco:

## in Pecora
sub colore_di_default { "bianco" }

E poi, per evitare di doverne definire uno per ogni classe aggiuntiva, definiremo direttamente in Animale un metodo a mo' di "rete di sicurezza" che funga da "default default":

## in Animale
sub colore_di_default { "marrone" }

Ora, visto che nome e chiamato sono gli unici metodi che esaminano la "struttura" dell'oggetto, il resto dei metodi può rimanere lo stesso, dunque parla funziona ancora come prima.

Un cavallo di un altro colore

Avere tutti i nostri cavalli di colore marrone sarebbe noioso. Aggiungiamo dunque un metodo o due per ottenere e modificare il colore.

## in Animale
sub colore {
  $_[0]->{Colore}
}
sub cambia_colore {
  $_[0]->{Colore} = $_[1];
}

Notate il modo alternativo per accedere agli argomenti: viene usato direttamente $_[0], piuttosto che uno shift. (Questo ci fa risparmiare un po' di tempo per un qualcosa che può essere invocato di frequente). E ora possiamo aggiustare il colore al Sig. Ed:

my $parlante = Cavallo->chiamato("Sig. Ed");
$parlante->cambia_colore("bianco-e-nero");
print $parlante->nome, " e` di colore ", $parlante->colore, "\n";

che produce:

Sig. Ed e` di colore bianco-e-nero

Sommario

Dunque, a questo punto abbiamo metodi di classe, costruttori, metodi di istanza, dati di istanza ed anche metodi per accedere ai dati. Ma questo è solo l'inzio di cosa può offrire il Perl. Non abbiamo ancora inziato a parlare dei metodi di accesso che funzionano sia in lettura sia in scrittura, dei distruttori, della notazione a oggetto indiretto, delle sotto-classi che aggiungono dati di istanza, dei dati locali alle classi, dell'overloading, dei test isa e can, della classe UNIVERSAL, e di un sacco di altre cose. Tutto ciò viene descritto dal resto della documentazione Perl. Ad ogni modo, speriamo che questo documento vi sia servito per prendere il via.

ULTERIORI INFORMAZIONI

Per ulteriori informazioni, consultate perlobj (per tutti quei dettagli sugli oggetti in Perl, ora che avete visto le basi), perltoot (il tutorial per chi conosce già gli oggetti), perltootc (che ha a che fare con i dati delle classi), perlbot (per qualche altro trucco), e i libri quali l'eccellente Object Oriented Perl di Damian Conway.

Alcuni moduli che possono dimostarsi interessanti sono: Class::Accessor, Class::Class, Class::Contract, Class::Data::Inheritable, Class::MethodMaker e Tie::SecureHash

COPYRIGHT

Copyright (c) 1999, 2000 di Randal L. Schwartz e Stonehenge Consulting Services, Inc. È permessa la distribuzione di questo documento, integro, assieme alla distribuzione Perl, e in accordo con le licenze della distribuzione Perl; i documenti derivati devono includere, intatta, questa nota di copyright.

Parti di questo testo sono tratte dal materiale didattico apparso originariamente nel corso Packages, References, Objects and Modules [Package, Riferimenti, Oggetti e Moduli, NdT] tenuto dagli istruttori di Stonehenge Consulting Services, Inc, e sono usate con l'autorizzazione.

Parti di questo testo sono tratte da materiale apparso originariamente su Linux Magazine e sono usate con l'autorizzazione.

Copyright (c) 1999, 2000 by Randal L. Schwartz and Stonehenge Consulting Services, Inc. Permission is hereby granted to distribute this document intact with the Perl distribution, and in accordance with the licenses of the Perl distribution; derived documents must include this copyright notice intact.

Portions of this text have been derived from Perl Training materials originally appearing in the Packages, References, Objects, and Modules course taught by instructors for Stonehenge Consulting Services, Inc. and used with permission.

Portions of this text have been derived from materials originally appearing in Linux Magazine and used with permission.

TRADUZIONE

Versione

La versione su cui si basa questa traduzione è ottenibile con:

perl -MPOD2::IT -e print_pod perlboot

Per maggiori informazioni sul progetto di traduzione in italiano si veda http://pod2it.sourceforge.net/ .

Traduttore

Traduzione a cura di dree.

Revisore

Revisione a cura di Gianni Ceccarelli e Hakim Cassimally.