=head1 NOME perltoot - Guida al Perl orientato agli oggetti, a cura di Tom =head1 DESCRIZIONE La programmazione orientata agli oggetti E alla moda di questi giorni. Alcuni manager preferirebbero gli oggetti all'uovo di Colombo. PerchE? Che cosa c'E di cosE speciale in un oggetto? Che cosa I> un oggetto innanzitutto? Un oggetto non E nient'altro che un modo per infilare funzionamenti complessi in un grazioso pacchetto semplice da usare. (Questo E ciE che i professori chiamano astrazione). Alcune persone in gamba che non hanno di meglio da fare che sedere intorno ad un tavolo per settimane al fine di risolvere problemi veramente complicati, creano questi splendidi oggetti che possono essere utilizzati anche da persone normali. (Questo E ciE che i professori chiamano riutilizzo del software). Gli utenti (cioE i programmatori) possono giocare quanto vogliono con questo pacchettino, ma non debbono aprirlo e far confusione con ciE che c'E dentro. Esattamente come un costoso macchinario, il contratto dice che la garanzia E nulla una volta aperto il coperchio. Quindi non fatelo. Il cuore degli oggetti E la classe, un piccolo namespace [spazio dei nomi, NdT] privato e protetto pieno di dati e funzioni. Una classe E un insieme di routine correlate che affrontano lo stesso ambito di problemi. Potete pensare ad essa come a un tipo definito dall'utente. Il meccanismo dei package del Perl, usato anche per moduli piE tradizionali, E usato pure per moduli di classi. Gli oggetti "vivono" in una classe, ciE vuol dire che appartengono a un qualche package. PiE spesso che no, la classe fornisce l'utente di pacchettini. Questi pacchettini sono gli oggetti. Sanno a quale classe appartengono e come debbono comportarsi. Gli utilizzatori chiedono alla classe di fare qualcosa, come "dammi un oggetto". Oppure possono chiedere a uno di questi oggetti di fare qualcosa. Chiedere ad una classe di fare qualcosa per voi si chiama I. Chiedere ad un oggetto di fare qualcosa per voi si chiama I. Chiedere ad una classe (di solito) o ad un oggetto (talvolta) di restituirvi un oggetto si chiama I, che E semplicemente un tipo particolare di metodo. Fin qui siamo d'accordo, ma in che cosa differisce un oggetto da qualsiasi altro tipo di dato in Perl? Che cos'E un oggetto I; cioE, qual'E il suo tipo fondamentale? La risposta alla prima domanda E semplice. Un oggetto E differente da qualsiasi altro tipo di dato in Perl in una ed una sola maniera: potete dereferenziarlo utilizzando non solamente indici numerici o alfanumerici come con semplici array e hash, ma con chiamate esplicite di subroutine. In una parola, con i I. La risposta alla seconda domanda E che un oggetto E un riferimento, e non un riferimento qualsiasi, badate bene, ma uno sul cui referente E stato eseguito un I() [consacrazione, NdT] in una particolare classe (leggi: package). Che tipo di riferimento?? Beh, la risposta a questo E un pE meno concreta. Questo perchE in Perl il progettista della classe puE impiegare qualsiasi tipo di riferimento gli piaccia come tipo di dato intrinseco sottostante. Potrebbe essere uno scalare, un array o un riferimento ad un hash. Potrebbe anche trattarsi di un riferimento a un codice. Ma data la sua innata flessibilitE, un oggetto E di solito un riferimento ad un hash. =head1 Creare una Classe Prima di creare una classe, dovete decidere che nome darle. Questo perchE il nome della classe (package) decide il nome del file utilizzato per ospitarla, esattamente come con i normali moduli. Quindi, quella classe (package) dovrebbe fornire uno o piE modi per generare oggetti. Alla fine, dovrebbe fornire meccanismi per permettere agli utilizzatori dei propri oggetti di manipolare indirettamente questi oggetti a distanza. Per esempio, facciamo un semplice modulo classe Persona. Esso viene archiviato nel file Persona.pm. Se la classe si fosse chiamata Brava::Persona, sarebbe stata archiviata nel file Brava/Persona.pm, e il suo package sarebbe diventato Brava::Persona invece della sola Persona. (In un personal computer con un sistema operativo diverso da Unix o Plan 9, ma con qualcosa come Mac OS o VMS, il separatore di directory puE essere diverso, ma il principio E lo stesso). Non assumete che ci sia alcuna relazione formale tra i moduli in base ai nomi delle loro directory. E esclusivamente una convenzione per raggruppare, e non ha effetto su ereditarietE, accessibilitE delle variabili o qualsiasi altra cosa. Per questo modulo non abbiamo intenzione di utilizzare Exporter, perchE siamo una classe di buone maniere che non esporta assolutamente niente. Per fabbricare oggetti, una classe deve avere un I. Un costruttore restituisce non solo un normale tipo di dato, ma un oggetto completamente nuovo di quella classe. Di questa stregoneria si occupa la funzione bless(), il cui solo scopo E di abilitare il suo referente ad essere usato come un oggetto. Ricordatevi: il fatto di essere un oggetto significa solamente che i metodi possono essere chiamati attraverso di esso. BenchE un costruttore possa essere chiamato in qualsiasi modo vogliate, sembra che molti programmatori preferiscano chiamarli new() [nuovo, NdT]. Ad ogni modo, new() non E una parola riservata, e una classe non E obbligata a contenerla. Pare che alcuni programmatori utilizzino una funzione con lo stesso nome della classe come costruttore. =head2 Rappresentazione di un Oggetto Il meccanismo di gran lunga piE utilizzato in Perl per rappresentare un record Pascal, uno struct C, o una classe C++ E un hash anonimo. Questo perchE un hash ha un numero arbitrario di campi dati, ognuno convenientemente acceduto da un nome arbitrario di vostra creazione. Se state solamente emulando qualcosa come uno struct, vi imbatterete in qualcosa come: $rec = { nome => "Gianni", eta => 23, compagni => [ "Norberto", "Raffaele", "Pino"], }; Se ve la sentite, potete aggiungere un po' di distinzione visiva mettendo in maiuscolo le chiavi dell'hash: $rec = { NOME => "Gianni", ETA => 23, COMPAGNI => [ "Norberto", "Raffaele", "Pino"], }; E in questo modo potete chiamare C<< $rec->{NOME} >> per ottenere "Gianni", o C<< @{ $rec->{COMPAGNI} } >> per ottenere "Norberto", "Raffaele", e "Pino". Questo stesso modello E spesso usato per le classi, sebbene non sia considerato l'apice della correttezza della programmazione, da gente nuova al concetto di classe, prendere possesso di un oggetto, accedendo in modo sfacciato ai suoi dati membri direttamente. Parlando in generale, un oggetto dovrebbe essere considerato come una misteriosa struttura dati che potete accedere con i I. Visivamente, le chiamate di metodi assomigliano alla dereferenziazione di un riferimento utilizzando un nome di una funzione anzichE con parentesi graffe o tonde. =head2 Interfaccia di una Classe Alcuni linguaggi forniscono un'interfaccia sintattica formale per i metodi di una classe, ma non il Perl. Si affida a voi per leggere la documentazione di ogni classe. Se provate a chiamare un metodo indefinito di un oggetto, il Perl non protesterE, ma il programma attiverE una eccezione a tempo di esecuzione. Allo stesso modo, se chiamate un metodo che si aspetta un numero primo come argomento, con un numero non primo, non potete aspettarvi che il compilatore individui questa discrepanza. (Beh, potete aspettarvi ciE che volete, ma non accadrE). Supponete di avere un utente ben educato della vostra classe Persona, qualcuno che ha letto la documentazione che spiega l'interfaccia stabilita. Vediamo come puE essere utilizzata la classe Persona: use Persona; $egli = Persona->new(); $egli->nome("Gianni"); $egli->eta(23); $egli->compagni( "Norberto", "Raffaele", "Pino" ); push @Ogni_Record, $egli; # salva l'oggetto in un array per dopo printf "%s ha %d anni.\n", $egli->nome, $egli->eta; print "I suoi compagni sono: ", join(", ", $egli->compagni), "\n"; printf "Il nome dell'ultimo record e` %s\n", $Ogni_Record[-1]->nome; Come potete vedere, l'utente della classe non sa (o almeno, non ha alcun interesse a sapere) che l'oggetto ha una particolare implementazione invece di un'altra. L'interfaccia alla classe ed ai suoi oggetti avviene esclusivamente tramite metodi, e questo E tutto ciE di cui l'utilizzatore della classe dovrebbe mai occuparsi. =head2 Costruttori e Metodi di Istanze Eppure, I deve sapere che cosa c'E nell'oggetto. E quel qualcuno E la classe. Essa implementa metodi che il programmatore utilizza per accedere all'oggetto. Qui vedrete come implementare la classe Persona utilizzando l'idioma standard hash-riferimento-come-un-oggetto. Costruiremo un metodo di classe chiamato new() che agisce come costruttore, e tre metodi di oggetto chiamati nome(), eta() e compagni() per ottenere dati di singoli oggetti nascosti nel nostro hash anonimo. package Persona; use strict; ######################################################### ## il costruttore di oggetto (versione semplicistica) ## ######################################################### sub new { my $self = {}; $self->{NOME} = undef; $self->{ETA} = undef; $self->{COMPAGNI} = []; bless($self); # ma vedi sotto return $self; } ################################################# ## metodi per accedere dati di singoli oggetti ## ## ## ## Con argomenti, impostano il valore. Senza ## ## di essi, recuperano il/i valore/i. ## ################################################# sub nome { my $self = shift; if (@_) { $self->{NOME} = shift } return $self->{NOME}; } sub eta { my $self = shift; if (@_) { $self->{ETA} = shift } return $self->{ETA}; } sub compagni { my $self = shift; if (@_) { @{ $self->{COMPAGNI} } = @_ } return @{ $self->{COMPAGNI} }; } 1; # cosi` il require o l'use ha successo Abbiamo creato tre metodi per accedere ai dati di un oggetto, nome(), eta() e compagni(). Questi sono tutti sostanzialmente simili. Se chiamati con un argomento, impostano il campo appropriato; altrimenti restituiscono il valore contenuto in quel campo, ovvero il valore corrispondente a quella chiave dell'hash. =head2 Progetti per il Futuro: Costruttori Migliori Anche se a questo punto potete anche ignorarne il significato, un giorno vi dovrete preoccupare dell'ereditarietE. (Potete tranquillamente ignorarla per adesso e preoccuparvi dopo, se vi pare). Per assicurarvi che tutto funzioni alla perfezione, dovete usare la forma con doppio argomento di bless(). Il secondo argomento E la classe nella quale al referente verrE applicata la funzione bless(). Non assumendo la nostra classe come secondo argomento di default ed usando invece la classe ricevuta come argomento, creiamo in nostro costruttore ereditabile. sub new { my $classe = shift; my $self = {}; $self->{NOME} = undef; $self->{ETA} = undef; $self->{COMPAGNI} = []; bless ($self, $classe); return $self; } Questo E tutto ciE che concerne i costruttori. Questi metodi danno vita agli oggetti, restituendo pacchetti graziosi e misteriosi all'utilizzatore, per essere usati in seguito con chiamate ai metodi. =head2 Distruttori Ogni storia ha un inizio ed una fine. L'inizio della storia di un oggetto E il suo costruttore, chiamato esplicitamente quando l'oggetto prende vita. Ma la fine della sua storia E il I, un metodo chiamato implicitamente quando un oggetto perde la vita. Qualsiasi codice di clean-up [pulizia, NdT] dedicato ai singoli oggetti E posto nel distruttore, che deve (in Perl) essere chiamato DESTROY [distruggi, NdT]. Se i costruttori possono avere nomi arbitrari, perchE i distruttori no? PerchE mentre i costruttori sono chiamati esplicitamente, i distruttori non lo sono. La distruzione avviene automaticamente attraverso il sistema di garbage collection (GC) [raccolta di immondizia, NdT] del Perl, che E un sistema di GC fondato sui riferimenti, veloce ma talvolta pigro. Per sapere che cosa chiamare, il Perl insiste sul fatto che il distruttore sia chiamato DESTROY. La nozione del Perl sul tempo esatto per la chiamata al distruttore non E al momento ben definita, ecco perchE i vostri distruttorinon dovrebbero fare affidamento sul quando vengono chiamati. PerchE DESTROY viene scritto completamente maiuscolo? Il Perl talvolta utilizza nomi di funzione completamente maiuscoli come convenzione per indicare che la funzione viene in qualche modo chiamata automaticamente dal Perl. Di questa categoria fanno parte BEGIN, END, AUTOLOAD, piE tutti i metodi utilizzati da oggetti legati con la funzione tie(), descritti in L. In linguaggi di programmazione orientati agli oggetti veramente buoni, l'utente non si cura di quando il distruttore viene chiamato. Accade quando ci si aspetta che accada. In linguaggi a basso livello del tutto privi di GC, non c'E modo di fare affidamento sul fatto che ciE avvenga al momento giusto, cosE il programmatore deve esplicitamente chiamare il distruttore per pulire memoria e stato, incrociando le dita che sia il tempo giusto per farlo. A differenza del C++, in Perl non E quasi mai necessario un distruttore di oggetto, e anche quando lo E, l'invocazione esplicita non E necessaria. Nel caso della nostra classe Persona, non abbiamo bisogno del distruttore perchE il Perl si prende cura di cose di poco conto come la deallocazione della memoria. La sola situazione nella quale il GC basato sui riferimenti del Perl non funziona E quando si trova di fronte a una struttura dati circolare, del tipo: $questo->{QUALCOSA} = $questo; In tal caso, dovete eliminare l'autoriferimento manualmente se volete che il vostro programma non sprechi memoria. BenchE dichiaratamente incline all'errore, questo E il meglio che possiamo fare al momento. Nondimeno, E sicuro che al termine del vostro programma i distruttori degli oggetti siano chiamati adeguatamente. Quindi siete garantiti dal fatto che un oggetto I viene propriamente distrutto, ad eccezione del caso unico in cui un programma non termina mai. (Se eseguite codice Perl immerso in un'altra applicazione la fase di GC avviene un po' piE frequentemente - ogni qual volta un thread si esaurisce). =head2 Altri Metodi di Oggetto I metodi di cui abbiamo parlato finora sono stati costruttori o semplici "metodi per dati", interfacce a dati archiviati nell'oggetto. Questi sono piE o meno come i dati membro di oggetti nell'accezione del C++, ad eccezione del fatto che non sono accessibili dall'esterno come dati. Al contrario, dovrebbero solamente accedere indirettamente ai dati dell'oggetto tramite i suoi metodi. Questa E una regola importante: in Perl, l'accesso ai dati di un oggetto dovrebbe essere I effettuato attraverso i metodi. Il Perl non pone restrizioni su chi puE usare quali metodi. La distinzione pubblico-contro-privato E una convenzione, non sintassi. (Beh, a meno che non utilizziate il modulo Alias descritto sotto in L). Talvolta vedrete nomi di metodo iniziare o terminare con uno o due trattini bassi. Questa marcatura E una convenzione per indicare che i metodi sono privati per quella sola classe e talvolta per i suoi stretti conoscenti, le sue immediate sottoclassi. Ma questa distinzione non E forzata dal Perl in sE. Sta al programmatore seguire questo comportamento. Non c'E ragione di limitare i metodi al semplice accesso ai dati. I metodi possono fare ogni cosa. Il punto chiave E che sono invocati per mezzo dell'oggetto o della classe. Diciamo che vorremmo metodi di oggetto che fanno piE che recuperare o impostare un campo particolare. sub esclama { my $self = shift; return sprintf "Ciao, sono %s, di %d anni, lavoro con %s", $self->{NOME}, $self->{ETA}, join(", ", @{$self->{COMPAGNI}}); } O forse anche uno come questo: sub buon_compleanno { my $self = shift; return ++$self->{ETA}; } Alcuni potrebbero obiettare che si dovrebbe procedere cosE: sub esclama { my $self = shift; return sprintf "Ciao, sono %s, di %d anni, lavoro con %s", $self->nome, $self->eta, join(", ", $self->compagni); } sub buon_compleanno { my $self = shift; return $self->eta( $self->eta() + 1 ); } Ma dato che questi metodi sono tutti eseguiti nella stessa classe, ciE puE non essere critico. Ci sono dei compromessi da fare. L'utilizzo diretto dell'hash E piE veloce (circa un ordine di magnitudo piE veloce, infatti), ed E piE conveniente quando volete fare degli inserimenti in stringhe. Ma l'utilizzo di metodi (l'interfaccia esterna) occulta internamente non solo gli utenti della vostra classe ma anche voi stessi da cambiamenti nella rappresentazione dei vostri dati. =head1 Dati della Classe Che dire a proposito dei "dati della classe", elementi di dati comuni ad ogni oggetto di una classe? Per quale scopo li vorreste? Dunque, nella vostra classe Persona, potreste voler tener conto del totale della popolazione vivente. Come implementereste ciE? I creare una variabile globale chiamata $Persona::Censo. Ma forse la sola ragione per cui lo fareste E se voi I che sia possibile accedere ai dati della vostra classe direttamente. Qualcuno potrebbe semplicemente prendere $Persona::Censo e farne qualsiasi cosa. Forse questo va bene nella vostra progettazione. Potreste anche concepibilmente volerla rendere una variabile esportata. Per essere esportabile, una variabile deve essere globale a tutto il package. Se questo fosse un modulo tradizionale piuttosto che uno orientato agli oggetti, potreste farlo. BenchE ci si aspetti questo approccio nella maggior parte dei moduli tradizionali, E una forma generalmente considerata alquanto mediocre nella maggior parte dei moduli a oggetti. In un modulo a oggetti, dovreste costruire un velo protettivo per separare l'interfaccia dall'implementazione. Fornite cosE un metodo di classe per accedere ai dati della classe cosE come fornite metodi di oggetti per accedere ai dati di oggetti. CosE, I sempre mantenere $Censo come una variabile globale del package e affidarvi ad altro per onorare il contratto con il modulo e di conseguenza non mettere le mani nella sua implementazione. Potreste anche essere superscaltri e rendere $Censo una variabile legata con tie() cosE come viene descritto in L, in modo da intercettare tutti gli accessi. Ma piE spesso che no, vorrete solamente rendere il dato della vostra classe una variabile lessicale con campo di visibilitE limitato al file. Per far ciE, ponete semplicemente questa in cima al file: my $Censo = 0; Anche se il campo di visibilitE del my() si estingue normalmente con il blocco nel quale E dichiarato (in questo caso l'intero file richiamato con use o require), il deep binding [legame profondo, NdT] del Perl per le variabili lessicali garantisce che la variabile non sarE deallocata, rimanendo accessibile alle funzioni dichiarate all'interno dello stesso ambito. PerE ciE non funziona con variabili globali alle quali si diano dati valori temporanei con local(). Indipendentemente dal fatto che vogliate lasciare $Censo come una variabile globale al package o ne facciate una lessicale con campo di visibilitE limitato al file, dovrete implementare questi cambiamenti nel vostro costruttore Persona::new() : sub new { my $classe = shift; my $self = {}; $Censo++; $self->{NOME} = undef; $self->{ETA} = undef; $self->{COMPAGNI} = []; bless ($self, $classe); return $self; } sub popolazione { return $Censo; } Adesso che abbiamo fatto ciE, sicuramente necessitiamo di un distruttore per il quale, quando una Persona E distrutta, il $Censo diminuisce. Possiamo fare in questo modo: sub DESTROY { --$Censo } Notate come non ci sia memoria da deallocare nel distruttore? Il Perl si prende cura di questo compito tutto da solo. In alternativa, potreste utilizzare il modulo Class::Data::Inheritable [Inheritable = Ereditabile, NdT] da CPAN. =head2 Accedere ai dati di una classe In fin dei conti questo non sembra proprio un buon modo per continuare a manipolare dati di una classe. Una buona regola scalabile E che I. Altrimenti non state costruendo una classe scalabile ed ereditabile. L'oggetto deve rappresentare il punto d'incontro per tutte le operazioni, specialmente da un metodo di oggetto. Le variabili globali (dati di classi) sarebbero in un certo senso nel "package" sbagliato della vostra classe derivata. In Perl, i metodi vengono eseguiti nel contesto della classe nella quale sono stati definiti, I in quello dell'oggetto che gli ha attivati. Per cui, la visibilitE in termini di namespace delle variabili globali al package nei metodi non ha alcuna relazione con l'ereditarietE. Tutto chiaro? Forse no. Ok, diciamo che qualche altra classe ha "preso in prestito" (beh, ereditato) il metodo DESTROY come E stato definito sopra. Quando questi oggetti sono distrutti, la variabile originale $Censo viene alterata, non quella nel namespace del package della nuova classe. Forse questo E ciE che volete, ma probabilmente non lo E. Ecco come aggiustare il tutto. Immagazzineremo un riferimento ai dati nel valore acceduto dalla chiave hash "_CENSO". PerchE il trattino basso? Beh, piE che altro perchE un trattino basso iniziale evoca giE una forte sensazione di magico a un programmatore C. E solamente una costruzione mnemonica per ricordarci che questo campo E speciale e non deve essere utilizzato come dato membro pubblico come lo sono NOME, ETA, e COMPAGNI. (PoichE stiamo sviluppando questo codice sotto la direttiva strict, prima della versione 5.004 del Perl dobbiamo racchiudere tra doppi apici il nome del campo). sub new { my $classe = shift; my $self = {}; $self->{NAME} = undef; $self->{ETA} = undef; $self->{COMPAGNI} = []; # dati "privati" $self->{"_CENSO"} = \$Censo; bless ($self, $classe); ++ ${ $self->{"_CENSO"} }; return $self; } sub popolazione { my $self = shift; if (ref $self) { return ${ $self->{"_CENSO"} }; } else { return $Censo; } } sub DESTROY { my $self = shift; -- ${ $self->{"_CENSO"} }; } =head2 Metodi di Debug Per una classe E tipico possedere un meccanismo di debug. Per esempio, potreste voler vedere quando gli oggetti vengono creati o distrutti. Per far ciE aggiungiamo una variabile lessicale di debug con campo di visibilitE limitato al file. Per questo, richiamiamo il modulo standard Carp per trasmettere i nostri messaggi warning e fatal. In tal modo i messaggi verranno restituiti con il nome del file ed il numero di linea del chiamante invece del vostro; se avessimo voluto che fossero dalla nostra prospettiva, avremmo semplicemente utilizzato die() e warn() direttamente invece di croak() e carp() rispettivamente. use Carp; my $Debugging = 0; Adesso aggiungiamo un nuovo metodo di classe per accedere alla variabile. sub debug { my $classe = shift; if (ref $classe) { confess "Metodo di classe chiamato come metodo di oggetto" } unless (@_ == 1) { confess "utilizzo: NOMECLASSE->debug(livello)" } $Debugging = shift; } Adesso aggiustiamo il DESTROY in modo da farlo lamentare un po' quando l'oggetto moribondo si estingue: sub DESTROY { my $self = shift; if ($Debugging) { carp "Distruggo $self " . $self->nome } -- ${ $self->{"_CENSO"} }; } Si potrebbe concepibilmente costruire uno stato di debug per ogni oggetto. In questo modo potreste chiamare entrambi: Persona->debug(1); # l'intera classe $egli->debug(1); # solo l'oggetto Per far ciE, il nostro metodo di debug deve comportarsi in maniera "bimodale", funzionando per classi I per oggetti. Di conseguenza, aggiustiamo i metodi debug() e DESTROY come segue: sub debug { my $self = shift; confess "utilizzo: coso->debug(livello)" unless @_ == 1; my $livello = shift; if (ref($self)) { $self->{"_DEBUG"} = $level; # solo me stesso } else { $Debugging = $level; # l'intera classe } } sub DESTROY { my $self = shift; if ($Debugging || $self->{"_DEBUG"}) { carp "Distruggo $self " . $self->nome; } -- ${ $self->{"_CENSO"} }; } Che cosa succede se una classe derivata (che chiameremo Impiegato) eredita metodi dalla classe di base Persona? Allora C<< Impiegato->debug() >>, quando chiamato come metodo di una classe, manipolerE $Persona::Debugging invece di $Impiegato::Debugging. =head2 Distruttori di Classi Il distruttore di oggetti gestisce la terminazione di ogni oggetto distinto. Ma talvolta E necessario fare un po' di pulizia quando l'intera classe viene messa fuori uso, che al momento succede solo quando il programma termina. Per costruire un tale I, create una funzione di nome END nel package della sua classe. Funziona esattamente come la funzione END nei moduli tradizionali, ovvero viene chiamata ogniqualvolta il vostro programma termina a meno che non avvenga una eccezione o muoia a causa di un "uncaught signal" [segnale non catturato, NdT]. Per esempio, sub END { if ($Debugging) { print "Tutte le persone se ne vanno adesso.\n"; } } Quando il programma termina, tutti i distruttori di classi (le funzioni END) dovranno essere chiamate nell'ordine opposto di quello con cui sono state chiamate (ordinamento LIFO). =head2 Documentare l'Interfaccia Ed eccolo qua: vi abbiamo solo mostrato l'I di questa classe Persona. La sua I sarE la sua documentazione. Di solito questo significa inserirla nel formato pod ("plain old documentation") esattamente lE nello stesso file. Nel nostro esempio Persona, metteremmo la seguente documentazione ovunque nel file Persona.pm. Anche se sembra principalmente codice, non lo E. E documentazione immersa del tipo che verrebbe utilizzato dai programmi pod2man, pod2html, o pod2text. Il compilatore del Perl ignora totalmente la documentazione pod, cosE come i traduttori ignorano il codice. Segue un esempio di qualche documentazione pod che descrive l'interfaccia formale: =head1 NOME Persona - classe per implementare gente =head1 SINOSSI use Persona; ####################### # metodi della classe # ####################### $og = Persona->new; $conta = Persona->popolazione; ############################## # metodi per dati di oggetto # ############################## ### ottenere versioni ### $chi = $og->nome; $anni = $og->eta; @amici = $og->compagni; ### impostare versioni ### $og->name("Gianni"); $og->age(23); $og->compagni( "Norberto", "Raffaele", "Pino" ); ########################### # altri metodi di oggetto # ########################### $frase = $og->esclama; $og->buon_compleanno; =head1 DESCRIZIONE La classe Persona implementa bla bla bla bla.... Questo E tutto ciE da sapere sulle questioni di interfaccia e implementazione. Un programmatore che apre il modulo e si arrischia a compiere modifiche ai piccoli ingranaggi che si trovavano chiusi dal contratto di interfaccia ha annulla la garanzia, e non dovreste preoccuparvi della sua sorte. =head1 Aggregazione Supponiamo che piE tardi vogliate cambiare la classe per implementare nomi migliori. Forse vorrete supportare sia nomi (o nomi di battesimo, senza riguardo per le rispettive religioni) che nomi di famiglia (o cognomi), oltre a soprannomi e titoli. Se gli utilizzatori della vostra classe Persona accedono correttamente ad essa attraverso la sua interfaccia documentata, allora potrete facilmente cambiare l'implementazione sottostante. Se non lo fanno, allora perdono ed E loro la colpa della rottura del contratto e dell'annullamento della garanzia. Per far ciE, creiamo un'altra classe chiamata Nomecompleto. Come sarE la classe Nomecompleto? Per rispondere a questa domanda, dovete prima immaginare come volete usarla. Potreste farlo in questo modo: $egli = Persona->new(); $egli->nomecompleto->titolo("San"); $egli->nomecompleto->battesimo("Tommaso"); $egli->nomecompleto->cognome("D'Aquino"); $egli->nomecompleto->soprannome("Tommy"); printf "Il suo nome normale E %s\n", $egli->nome; printf "Ma il suo nome vero E %s\n", $egli->nomecompleto->come_stringa; Ok. Per far ciE, cambiamo Persona::new() in modo che supporti un campo per il nome completo come segue: sub new { my $classe = shift; my $self = {}; $self->{NOMECOMPLETO} = Nomecompleto->new(); $self->{ETA} = undef; $self->{COMPAGNI} = []; $self->{"_CENSO"} = \$Censo; bless ($self, $classe); ++ ${ $self->{"_CENSO"} }; return $self; } sub nomecompleto { my $self = shift; return $self->{NOMECOMPLETO}; } Poi per supportare vecchio codice, definiamo Persona::nome() in questo modo: sub nome { my $self = shift; return $self->{NOMECOMPLETO}->soprannome(@_) || $self->{NOMECOMPLETO}->battesimo(@_); } Ecco la classe Nomecompleto. Sceglieremo la stessa tecnica di usare un riferimento ad un hash per mantenere i campi dei dati, e metodi con il nome appropriato per accederli: package Nomecompleto; use strict; sub new { my $classe = shift; my $self = { TITOLO => undef, BATTESIMO => undef, COGNOME => undef, SOPRANNOME => undef, }; bless ($self, $classe); return $self; } sub battesimo { my $self = shift; if (@_) { $self->{BATTESIMO} = shift } return $self->{BATTESIMO}; } sub cognome { my $self = shift; if (@_) { $self->{COGNOME} = shift } return $self->{COGNOME}; } sub soprannome { my $self = shift; if (@_) { $self->{SOPRANNOME} = shift } return $self->{SOPRANNOME}; } sub titolo { my $self = shift; if (@_) { $self->{TITOLO} = shift } return $self->{TITOLO}; } sub come_stringa { my $self = shift; my $nome = join(" ", @$self{'BATTESIME, 'COGNOME}); if ($self->{TITOLO}) { $nome = $self->{TITOLO} . " " . $nome; } return $nome; } 1; Infine, ecco il programma di test: #!/usr/bin/perl -w use strict; use Persona; sub END { mostra_censo() } sub mostra_censo () { printf "Popolazione attuale: %d\n", Persona->popolazione; } Persona->debug(1); mostra_censo(); my $egli = Persona->new(); $egli->nomecompleto->battesimo("Thomas"); $egli->nomecompleto->cognome("Aquinas"); $egli->nomecompleto->soprannome("Tommy"); $egli->nomecompleto->titolo("St"); $egli->eta(1); printf "%s E veramente %s.\n", $egli->nome, $egli->nomecompleto->come_stringa; printf "%s ha %d anni.\n", $egli->nome, $egli->eta; $egli->buon_compleanno; printf "%s ha %d anni.\n", $egli->nome, $egli->eta; mostra_censo(); =head1 EreditarietE Tutti i sistemi di programmazione orientata agli oggetti supportano una qualche nozione di ereditarietE. EreditarietE significa permettere a una classe di addossarsi su di un'altra in modo da non dover scrivere lo stesso codice ancora e ancora. Si tratta di riutilizzo di software, quindi collegato con la Pigrizia, la principale virtE di un programmatore. (I meccanismi di import/export nei moduli tradizionali sono anch'essi una forma di riutilizzo del codice, ma piE semplice della vera ereditarietE che troverete nei moduli oggetto). A volte la sintassi di ereditarietE E costruita nel nucleo del linguaggio, e a volte no. Il Perl non ha una sintassi speciale per specificare la classe (o le classi) dalla quale ereditare. Invece, sta tutto rigorosamente nella semantica. Ogni package puE avere una variabile chiamata @ISA ["IS A" in inglese significa "E UN", NdT], che gestisce l'ereditarietE (dei metodi). Se provate a invocare un metodo di un oggetto o una classe, e quel metodo non si trova nel package di quell'oggetto, allora il Perl scandisce gli altri package in @ISA per andare in cerca del metodo perduto. Come le variabili speciali dedicate ai package riconosciute da Exporter (tipo @EXPORT, @EXPORT_OK, @EXPORT_FAIL, %EXPORT_TAGS, e $VERSION), l'array @ISA I essere globale con campo di visibilitE esteso al package e non una variabile lessicale con campo di visibilitE limitato al file, creata con my(). La maggior parte delle classi hanno solo un elemento nel loro array @ISA. In questo caso, abbiamo quel che si chiama "ereditarietE singola". Si consideri la seguente classe: package Impiegato; use Persona; @ISA = ("Persona"); 1; Non un gran che, eh? Tutto ciE che fa per ora E caricare un'altra classe e dichiarare che questa erediterE metodi dall'altra classe se necessario. Non le abbiamo dato nessuno dei suoi metodi. Contiamo sul fatto che un Impiegato si comporti come una Persona. L'impostazione di una classe vuota, in questa maniera, si chiama "test per la sottoclasse vuota"; cioE, creare una classe derivata che non fa altro che ereditare da una classe di partenza. Se la classe di partenza originale E stata progettata correttamente, allora la nuova classe derivata puE essere utilizzata direttamente come un rimpiazzo per quella vecchia. CiE significa che dovreste essere in grado di scrivere un programma come questo: use Impiegato; my $impieg = Impiegato->new(); $impieg->nome("Gianni"); $impieg->eta(23); printf "%s ha %d anni.\n", $impieg->nome, $impieg->eta; Per correttezza di progettazione, intendiamo sempre utilizzare la forma a due argomenti di bless(), evitando l'accesso diretto a dati globali, e non esportando alcunchE. Se tornate indietro a guardare la funzione Persona::new() che abbiamo definito, siamo stati cauti a farlo. Ci sono un po' di dati del package utilizzati nel costruttore, ma il riferimento per far ciE E archiviato nell'oggetto stesso e tutti gli altri metodi accedono ai dati del package attraverso quel riferimento, per cui dovremmo essere a posto. Che cosa intendiamo per funzione Persona::new() -- non E in realtE un metodo? Beh, in principio sE. Un metodo E solo una funzione che accetta come primo argomento il nome di una classe (package) o un oggetto (riferimento al quale E stata applicata la funzione bless()). Persona::new() E la funzione che sia il metodo C<< Persona->new() >> che il metodo C<< Impiegato->new() >> finiscono per chiamare. Si osservi che benchE una chiamata di metodo appaia quasi come una chiamata di funzione, esse non sono assolutamente la stessa cosa, e se trattate le due alla stessa maniera, rimarrete presto con niente di piE che programmi che non funzionano. Anzitutto, le effettive convenzioni di chiamata sottostanti sono differenti: chiamate di metodi prendono un argomento extra. Secondariamente, le chiamate di funzione non effettuano l'ereditarietE mentre i metodi sE. Chaiata di metodo Chiamata di funzione risultante ----------- ------------------------ Persona->new() Persona::new("Persona") Impiegato->new() Persona::new("Impiegato") Quindi non utilizzate chiamate di funzione quando intendete chiamare un metodo. Se un impiegato E solo una Persona, questo non E molto interessante. Quindi aggiungiamo altri metodi. Daremo ai nostri impiegati campi di dato per accedere al loro salario, al loro identificatore di impiegato, e alla loro data di inizio. Se siete un po' stanchi di creare tutti questi metodi quasi identici solo per accedere ai dati dell'oggetto, non disperate. PiE tardi descriveremo molti comodi meccanismi per abbreviare tutto ciE. Nel frattempo, vediamo il modo diretto: sub salario { my $self = shift; if (@_) { $self->{SALARIO} = shift } return $self->{SALARIO}; } sub id_numero { my $self = shift; if (@_) { $self->{ID} = shift } return $self->{ID}; } sub data_inizio { my $self = shift; if (@_) { $self->{DATA_INIZIO} = shift } return $self->{DATA_INIZIO}; } =head2 Metodi overridden [scavalcati, NdT] Che cosa accade quando sia la classe derivata che la classe base definiscono lo stesso metodo? Beh, in questo caso ottenete la versione di quel metodo della classe derivata. Per esempio, vogliamo che il metodo compagni() invocato da un impiegato, agisca un po' diversamente. Invece di restituire la lista dei nomi dei compagni, restituiamo una stringa leggermente diversa. CosE facendo, questo: $impieg->compagni("Pietro", "Paolo", "Maria"); printf "I suoi compagni sono: %s\n", join(", ", $impieg->compagni); produrrE: I suoi compagni sono: COMPAGNO=PIETRO, COMPAGNO=PAOLO, COMPAGNO=MARIA Per far ciE, aggiungete solamente questa definizione nel file Impiegato.pm: sub compagni { my $self = shift; if (@_) { @{ $self->{COMPAGNI} } = @_ } return map { "COMPAGNO=\U$_" } @{ $self->{COMPAGNI} }; } Con questo, abbiamo appena dimostrato quel concetto altisonante conosciuto in alcuni ambienti come I. Abbiamo prelevato la forma ed il comportamento di un oggetto esistente, e quindi l'abbiamo alterato per soddisfare le nostre necessitE. Questa E una forma di Pigrizia. (Venire polimorfizzati E anche ciE che accade quando un mago decide che la forma di una rana sarebbe piE adatta a voi). Di quando in quando vorrete che la chiamata di un metodo attivi sia la versione della classe derivata (detta anche "sottoclasse") che la versione della classe base (detta anche "superclasse"). In pratica, costruttori e distruttori vogliono fare presumibilmente questo, ed ha anche senso, probabilmente, nel metodo debug() che abbiamo mostrato in precedenza. Per far ciE, aggiungiamo questo a Impiegato.pm: use Carp; my $Debugging = 0; sub debug { my $self = shift; confess "utilizzo: thing->debug(level)" unless @_ == 1; my $livello = shift; if (ref($self)) { $self->{"_DEBUG"} = $livello; } else { $Debugging = $livello; # l'intera classe } Persona::debug($self, $Debugging); # non provate a farlo } Come vedete, continuiamo a chiamare la funzione debug() del package Persona. Ma ciE E troppo debole per una buona progettazione. Che accade se Persona non ha una funzione debug(), ma sta ereditando il I metodo debug() da qualche altra parte? Sarebbe leggermente meglio fare Persona->debug($Debugging); Ma anche questo E eccessivamente hard-coded [cablato, NdT]. E alquanto meglio fare $self->Persona::debug($Debugging); che E uno strano modo per dire di iniziare a cercare il metodo debug() in Persona. Questa strategia viene usata piE spesso per metodi di oggetto overridden che per metodi di classe overridden. Qui c'E ancora qualcosa che va aggiustata. Abbiamo scritto per esteso il nome della superclasse. Questo in particolare non va bene se cambiate le classi dalle quali ereditate o ne aggiungete altre. Qui fortunatamente la pseudoclasse SUPER ci viene in aiuto. $self->SUPER::debug($Debugging); In questo modo inizia a cercare nella lista @ISA della mia classe. Comunque questo ha senso solamente I di una chiamata di metodo. Non provate ad accedere a niente in SUPER:: da nessun altra parte, perchE questo non esiste al di fuori di una chiamata di metodo overridden. Osservate che C si riferisce alla superclasse del package corrente, I alla superclasse di C<$self>. Qui lee cose si stanno facendo un po' complicare. Abbiamo fatto qualcosa che non andava fatto? Come prima, un modo per verificare se stiamo progettando una classe decente E per mezzo di una sottoclasse vuota di test. Dato che abbiamo giE una classe Impiegato che stiamo provando a verificare, sarebbe meglio avere una nuova sottoclasse vuota che deriva da Impiegato. Eccone una: package Capo; use Impiegato; # :-) @ISA = qw(Impiegato); E qui vediamo il programma di verifica: #!/usr/bin/perl -w use strict; use Capo; Capo->debug(1); my $capo = Capo->new(); $capo->nomecompleto->titolo("Don"); $capo->nomecompleto->cognome("Pichon Alvarez"); $capo->nomecompleto->battesimo("Federico Jesus"); $capo->nomecompleto->soprannome("Fred"); $capo->eta(47); $capo->compagni("Franco", "Felice", "Fausto"); printf "%s ha %d anni.\n", $capo->nomecompleto->come_stringa, $capo->age; printf "I suoi compagni sono: %s\n", join(", ", $capo->compagni); Eseguendolo, vedremo che tutto va ancora bene. Se volete scaricare dalla memoria il vostro oggetto in un formato carino, qualcosa come il modo in cui il comando 'x' funziona nel debugger, potete usare il modulo Data::Dumper da CPAN in questo modo: use Data::Dumper; print "Ecco il capo:\n"; print Dumper($capo); Che ci mostra qualcosa del genere: Ecco il capo: $VAR1 = bless( { _CENSO => \1, NOMECOMPLETO => bless( { TITOLO => 'Don', COGNOME => 'Pichon Alvarez', SOPRANNOME => 'Fred', BATTESIMO => 'Federico Jesus' }, 'Nomecompleto' ), ETA => 47, COMPAGNI => [ 'Franco', 'Felice', 'Fausto' ] }, 'CapE ); Hm.... qui manca qualcosa. Dove sono il salario, la data di inizio, e il numero di identificazione? Beh, non abbiamo mai assegnato loro alcun valore, nemmeno undef, quindi non saltano fuori nelle chiavi dell'hash. La classe Impiegato non ha un metodo new() di per sE, ed il metodo new() in Persona E a conoscenza di Impiegati. (E nemmeno dovrebbe saperlo: la corretta progettazione orientata agli oggetti impone che a una sottoclasse sia permesso di conoscere la sua immediata superclasse, ma mai il contrario). Quindi aggiustiamo Impiegato::new() in questa maniera: sub new { my $class = shift; my $self = $class->SUPER::new(); $self->{SALARIO} = undef; $self->{ID} = undef; $self->{DATA_INIZIO} = undef; bless ($self, $class); # nuova abilitazione, "riconsacrazione" return $self; } Adesso se scaricate dalla memoria un oggetto Impiegato o Capo, vedrete che adesso appariranno nuovi campi. =head2 EreditarietE Multipla Ok, pur correndo il rischio di confondere i principianti e annoiare i guru della programmazione orientata agli oggetti, E tempo di confessare che il sistema degli oggetti del Perl include quella controversa nozione conosciuta come ereditarietE multipla, o per brevitE EM. CiE significa che invece di avere una sola classe padre che a sua volta puE di per sE avere una classe padre, ecc., potete direttamente ereditare da due o piE genitori. E vero che alcuni utilizzi di EM possono mettervi nei guai, sebbene si spera non del tutto con il Perl come con linguaggi orientati agli oggetti in modo dubbio come il C++. Il modo in cui funziona in realtE E molto semplice: basta includere piE nomi di package nela lista @ISA. Quando per il Perl viene il tempo di andare alla ricerca di metodi per il vostro oggetto, va a vedere nell'ordine in ognuno di questi package. Beh, una specie. E in realtE una prima ricerca in profonditE (depth-first) totalmente ricorsiva. Considerate un gruppo di liste @ISA come queste: @Prima::ISA = qw( Alfa ); @Seconda::ISA = qw( Beta ); @Terza::ISA = qw( Prima Seconda ); Se avete un oggetto della classe Terza: my $ogg = Terza->new(); $ogg->gira(); Come troviamo un metodo gira (o un metodo new() per quel caso)? A causa del fatto che la ricerca E prima in profonditE, le classi verranno scandite nel seguente ordine: Terza, Prima, Alfa, Seconda, e Beta. In pratica, si sono visti pochi moduli di classe fare veramente uso della EM. Quasi sempre viene scelto un contenimento semplice di una classe anzichE l'EM. Ecco perchE il nostro oggetto Persona I un oggetto Nomecompleto. Questo non significa che I un Nomecompleto. Ad ogni modo, c'E un caso particolare nel quale l'EM in Perl E frequentemente utilizzata: quando si prendono in prestito i metodi di classe di un'altra classe. Questo E abbastanza comune specialmente se si ha a che fare con classi per le quali di solito non ci interessano gli oggetti di per sE, come Exporter, DynaLoader, AutoLoader, e SelfLoader. Queste classi non forniscono costruttori; esistono solo per il fatto che voi possiate far ereditare i loro metodi di classe. (Non E esattamente chiaro perchE qui E stata utilizzata l'ereditarietE invece dell'inclusione tradizionale di moduli). Per esempio, ecco l'array @ISA del modulo POSIX: package POSIX; @ISA = qw(Exporter DynaLoader); Il modulo POSIX non E in realtE un modulo orientato agli oggetti, ma allora non lo sono neppure Exporter o DynaLoader. Danno solo in prestito i comportamenti delle loro classi a POSIX. PerchE l'ereditarietE non viene molto utilizzata per i metodi di oggetto? Una ragione E che puE avere effetti collaterali complessi. Per prima cosa, il vostro grafo di ereditarietE (non piE un albero) puE confluire all'indietro alla stessa classe di base. Sebbene il Perl ci protegga dall'ereditarietE ricorsiva, avere semplicemente genitori che sono imparentati attraverso un avo un comune, sebbene suoni incestuoso, non E proibito. Cosa accadrebbe se nella nostra classe Terza di cui sopra volessimo che il metodo new() chiamasse anche entrambi i costruttori overridden nelle sue classi genitrici? La notazione SUPER troverebbe solo il primo. In piE, che accadrebbe se entrambe le classi Alfa e Beta avessero un avo in comune, chiamato Nullo? Se continuaste a percorrere l'albero di ereditarietE chiamando metodi overridden, finireste col chiamare Nullo::new() due volte, il che potrebbe proprio essere una cattiva idea. =head2 UNIVERSAL: La Radice di Tutti Gli Oggetti Non sarebbe conveniente se tutti gli oggetti avessero come radice un'unica classe base in comune? In questo modo potreste dare ad ogni oggetto dei metodi comuni senza dover aggiungerli a tutti quelli inclusi nelle liste @ISA. Beh, pare che ciE sia possibile. Non lo potete vedere, ma il Perl assume tacitamente ed irrevocabilmente che ci sia un elemento extra alla fine di @ISA: la classe UNIVERSAL. Nella versione 5.003, non vi si trovava alcun metodo predefinito, ma potevate aggiungerne a vostro piacimento. Comunque, dalla versione 5.004 (o qualche release sovversiva, come la 5.003_08), UNIVERSAL contiene giE alcuni metodi. Questi sono integrati nel vostro codice oggetto del Perl, quindi non necessitano di tempo extra per il caricamento. I metodi predefiniti comprendono isa(), can(), e VERSION(). isa() vi dice se un oggetto o una classe "E" un'altra senza dover voi stessi attraversare la gerarchia: $ha_io = $fd->isa("IO::Handle"); $discende_da_handle = IO::Socket->isa("IO::Handle"); Il metodo can(), chiamato in quell'oggetto o classe, ci informa se il suo argomento stringa E un metodo invocabile di quella classe. Infatti, restituisce un riferimento della funzione di quel metodo: $il_suo_metodo_print = $ogg->can('come_stringE); Infine, il metodo VERSION controlla se la classe (o la classe dell'oggetto) ha una variabile globale di package chiamata $VERSION che sia grande abbastanza, come in: Qualche_Modulo->VERSION(3.0); $la_sua_versione = $ogg->VERSION(); Ad ogni modo, di solito non siamo noi stessi a chiamare VERSION. (Ricordate che il nome di una funzione totalmente maiuscolo E una convenzione del Perl per indicare che la funzione sarE automaticamente chiamata dal Perl in una qualche maniera). In questo caso, ciE accade quando scrivete: use Qualche_Modulo 3.0; Se volevate aggiungere il controllo della versione nella vostra classe Persona introdotta precedentemente, aggiungete questo a Persona.pm: our $VERSION = '1.1'; e quindi in Impiegato.pm potete scrivere use Persona 1.1; E si assicurerE che abbiate almeno a disposizione quel numero di versione o piE alto. CiE non E lo stesso che caricare il numero di versione corretto. Non esiste al momento nessun meccanismo per l'installazione concorrente di versioni multiple di un modulo. Deplorevolmente. =head1 Rappresentazioni Alternative di Oggetti Non E obbligatorio implementare oggetti come riferimenti a hash. Un oggetto puE essere qualsiasi tipo di riferimento fintantochE il suo referente sia stato debitamente consacrato con bless(). CiE significa che uno scalare, una lista, e un riferimento a codice possono esserne il bersaglio. Uno scalare funzionerebbe nel caso in cui l'oggetto abbia solo un semplice dato da mantenere. Un array potrebbe funzionare in molti casi, ma ciE implicherebbe complicazioni per l'ereditarietE dato che dovreste inventare nuovi indici per la classe derivata. =head2 Array come oggetti Se l'utilizzatore della vostra classe onora il contratto e rispetta l'interfaccia pubblicata, allora se ve la sentite potete cambiare la sua implementazione sottostante. Ecco qui un'altra implementazione che si adegua alle stesse specifiche di interfaccia. Questa volta utilizzeremo un riferimento ad una lista invece di un riferimento ad un hash per rappresentare un oggetto. package Persona; use strict; my($NOME, $ETA, $COMPAGNI) = ( 0 .. 2 ); #################################################### ## il costruttore di oggetto (versione con lista) ## #################################################### sub new { my $self = []; $self->[$NOME] = undef; # questo non e` necessario $self->[$ETA] = undef; # nemmeno questo $self->[$COMPAGNI] = []; # ma questo non lo e`, veramente bless($self); return $self; } sub nome { my $self = shift; if (@_) { $self->[$NOME] = shift } return $self->[$NOME]; } sub eta { my $self = shift; if (@_) { $self->[$ETA] = shift } return $self->[$ETA]; } sub compagni { my $self = shift; if (@_) { @{ $self->[$COMPAGNI] } = @_ } return @{ $self->[$COMPAGNI] }; } 1; # quindi il require o l'use hanno successo Potete supporre che l'accesso alla lista sia molto piE veloce dell'accesso all'hash, ma sono in realtE comparabili. L'array E I piE veloce, ma non piE del dieci o quindici per cento, anche quando rimpiazzate le variabili come $ETA con numeri letterali, come 1. Una differenza maggiore tra i due approcci puE trovarsi nell'utilizzo della memoria. La rappresentazione di un hash richiede piE memoria della rappresentazione di un array perchE E necessario allocare la memoria sia per le chiavi che per i valori. Ad ogni modo, non E cosE male, specialmente dalla versione 5.004, la memoria E allocata solo una volta per una data chiave di hash, qualunque sia il numero degli hash che abbiano quella chiave. Ci si aspetta che in un futuro non ben identificabile anche queste differenze si assottiglieranno per via del fatto che saranno escogitate rappresentazioni piE efficienti. Ancora, il piccolo margine in termini di velocitE (e un po' piE grande in termini di memoria) E abbastanza da convincere alcuni programmatori a scegliere la rappresentazione con array per classi semplici. C'E ancora un piccolo problema di scalabilitE, perE, perchE piE in lE nella vita, quando ve la sentirete di creare sottoclassi, troverete semplicemente che gli hash funzionano meglio. =head2 Chiusure come Oggetti Utilizzare un riferimento a codice per rappresentare un oggetto offre delle possibilitE affascinanti. Possiamo creare una nuova funzione anonima (chiusura) che da sola in tutto il mondo puE vedere i dati dell'oggetto. Questo perchE mettiamo i dati in un hash anonimo che E visibile lessicalmente solo alla chiusura che creiamo, consacriamo con bless(), e restituiamo come oggetto. I metodi di questo oggetto effettuano la chiamata della chiusura come una semplice chiamata di subroutine, passandole il campo che ci interessa. (SE, la doppia chiamata di funzione E lenta, ma se volevate la velocitE, non avreste utilizzato per niente gli oggetti, eh? :-) L'utilizzo sarE simile al precedente: use Persona; $egli = Persona->new(); $egli->nome("Gianni"); $egli->eta(23); $egli->compagni( [ "Norberto", "Raffaele", "Pino" ] ); printf "%s ha %d anni.\n", $egli->nome, $egli->eta; print "I suoi compagni sono: ", join(", ", @{$egli->compagni}), "\n"; ma l'implementazione sarE radicalmente differente, forse anche in modo sublime: package Persona; sub new { my $classe = shift; my $self = { NOME => undef, ETA => undef, COMPAGNI => [], }; my $chiusura = sub { my $campo = shift; if (@_) { $self->{$campo} = shift } return $self->{$campo}; }; bless($chiusura, $classe); return $chiusura; } sub nome { &{ $_[0] }("NOME", @_[ 1 .. $#_ ] ) } sub eta { &{ $_[0] }("ETA", @_[ 1 .. $#_ ] ) } sub compagni { &{ $_[0] }("COMPAGNI", @_[ 1 .. $#_ ] ) } 1; Dato che questo oggetto si nasconde dietro a un riferimento a codice, risulterE probabilmente un po' misterioso per coloro che sono abituati ai linguaggi di programmazione procedurali standard o orientati agli oggetti piuttosto che ai linguaggi di programmazione funzionali dai quali le chiusure provengono. L'oggetto creato e restituito dal metodo new() non E di per sE un riferimento a dei dati, come abbiamo visto in precedenza. E un riferimento anonimo a codice che ha al suo interno l'accesso a una specifica versione (binding lessicale e instanziazione) dei dati dell'oggetto, che sono immagazzinati nella variabile privata $self. Sebbene questa sia la stessa funzione ogni volta, contiene versioni differenti di $self. Quando un metodo come C<$egli-Enome("Gianni")> viene invocato, il suo argomento zero-esimo implicito E l'oggetto che invoca -- esattamente come per tutte le chiamate di metodi. Ma in questo caso E il nostro riferimento a codice (qualcosa come un puntatore a funzioni in C++, ma con deep binding di variabili lessicali). Non c'E molto da fare con un riferimento a codice oltre a invocarlo, quindi questo E semplicememnte ciE che facciamo quando diciamo C<&{$_[0]}>. Questa E semplicemente una normale chiamata di funzione, non una invocazione di metodo. L'argomento iniziale E la stringa "NOME", ed ogni altro argomento rappresenta tutto ciE che viene passato al metodo stesso. Una volta che eseguiamo il codice all'interno della chiusura che E stata creata con new(), il riferimento all'hash $self diventa visibile. La chiusura carpisce il suo primo argomento ("NOME" in questo caso perchE ciE E quanto il metodo nome() gli ha passato), ed utilizza tale stringa come indice nell'hash privato nascosto nella sua unica versione di $self. Niente al mondo permetterE ad alcuno al di fuori del metodo in esecuzione di accedere a questi dati nascosti. Beh, quasi niente. I percorrere a singoli passi il programma utilizzando il debugger e trovare i pezzi quando siete nel metodo, ma gli altri non hanno scampo. Ecco, se questo non eccita gli appassionati di Scheme [un linguaggio multi-paradigma simile al Lisp, NdT], allora non so cosa lo farE. La traduzione di queste tecniche in C++, Java o in qualsiasi altro linguaggio statico-ottuso E lasciata come frivolo esercizio per gli affezionati di questi campi. Potreste addirittura aggiungere un po' di stranezze per mezzo della funzione caller() e imporre alla chiusura di agire se non chiamata attraverso il suo stesso package. Questo indubbiamnte soddisferebbe qualche fastidiosa faccenda che riguarda i puristi della programmazione e puritani affini. Se vi stavate chiedendo quando entra in gioco l'Arroganza, la terza principale virtE di un programmatore, qui lo vedrete. (PiE seriamente, Arroganza E solo l'orgoglio della maestria che deriva dall'aver scritto un bel po' di codice ben progettato). =head1 AUTOLOAD: Metodi Proxy L'utilizzo dell'Autoload [autocaricamento, NdT] E un modo per intercettare chiamate a metodi non definiti. Una routine di autoload puE scegliere di creare una nuova funzione sul posto, caricata da disco o forse solo valutata con eval() in loco. Questa strategia di definizione-sul-posto E il perchE del nome autoload. Ma questo E solo uno dei possibili approcci. Un altro E semplicemente di fare in modo che il metodo autocaricato stesso fornisca il servizio richiesto. Se utilizzato in questa maniera, potreste pensare ai metodi autocaricati come metodi "proxy". Quando il Perl tenta di chiamare una funzione indefinita in un determinato package e non la trova, cerca una funzione di nome AUTOLOAD in quello stesso package. Se esiste, viene chiamata dandole gli stessi parametri della funzione originale. Il nome della funzione per intero E salvato nella variabile globale di nome $AUTOLOAD dello stesso package. Una volta chiamata, la funzione puE fare ciE che vuole, compreso definire una nuova funzione con il nome giusto, e poi fare una cosa elegante tipo un C ad essa, eliminando se stessa dallo stack di chiamata. Che cosa ha che fare questo con gli oggetti? Dopo tutto, stiamo sempre parlando di funzioni, non metodi. Beh, dato che un metodo E semplicemente una funzione con un argomento extra e un po' di eleganza semantica riguardo alla sua localizzazione, possiamo utilizzare la tecnica di autoload anche per i metodi. Comunque, il Perl non inizia a cercare il metodo AUTOLOAD finchE non ha esaurito la ricerca ricorsiva attraverso @ISA. Alcuni programmatori hanno anche definito un metodo UNIVERSAL::AUTOLOAD per intrappolare chiamate di metodo non risolte per ogni tipo di oggetto. =head2 Autoload per Metodi di Dati Probabilmente inizierete ad essere un po' sospettosi riguardo al modo in cui il codice veniva duplicato quando vi avevamo mostrato la classe Persona, ed in seguito la classe Impiegato. I metodi utilizzati per accedere ai campi dell'hash apparivano virtualmente identici. Questo avrebbe dovuto stuzzicare quella grande virtE del programmatore, l'Impazienza, ma al momento la Pigrizia aveva vinto, e cosE non ci sono state conseguenze. I metodi proxy possono aggiustare la situazione. Invece di scrivere una nuova funzione ogni volta che vogliamo un nuovo campo dati, utilizziamo il meccanismo dell'autoload per generare (in realtE imitare) metodi sul posto. Per verificare che stiamo accedendo ad un membro valido, controlleremo con un campo C<_consentito>, che E un riferimento a un hash lessicale con campo di visibilitE limitato al file (come un C file static) di campi consentiti in questo record di nome %campi. PerchE il trattino basso? Per la stesso motivo per il quale lo abbiamo utilizzato per _CENSO: come un promemoria che significa "solo per uso interno". Ecco come appariranno il codice per l'inizializzazione del modulo ed il costruttore di classe utilizzando quest'approccio: package Persona; use Carp; our $AUTOLOAD; # e` globale rispetto al package my %campi = ( nome => undef, eta => undef, compagni => undef, ); sub new { my $classe = shift; my $self = { _consentito => \%campi, %campi, }; bless $self, $classe; return $self; } Se volessimo valori di default per il nostro record, potremmo inserirli dove abbiamo scritto C nell'hash %campi. Avete notato come abbiamo salvato un riferimento ai dati della nostra classe nell'oggetto stesso? Ricordate che E importante accedere ai dati della classe attraverso l'oggetto stesso invece di permettere ad ogni metodo di accedere direttamente a %campi, altrimenti non potreste avere una ereditarietE decente. PerE, la vera magia va a risiedere nel nostro metodo proxy, che gestirE tutte le chiamate a metodi non definiti per gli oggetti della classe Persona (o sottoclassi di Persona): E chiamato AUTOLOAD. Ancora, E scritto tutto in maiuscolo perchE viene invocato implicitamente dal Perl stesso, non direttamente dall'utilizzatore. sub AUTOLOAD { my $self = shift; my $tipo = ref($self) or croak "$self non e` un oggetto"; my $nome = $AUTOLOAD; $nome =~ s/.*://; # toglie il pezzo relativo ai nomi dei package unless (exists $self->{_consentito}->{$nome} ) { croak "Non posso accedere al campo `$nome' nella classe $tipo"; } if (@_) { return $self->{$nome} = shift; } else { return $self->{$nome}; } } Eccezionale vero? Tutto ciE che dobbiamo fare per aggiungere nuovi campi dati E modificare %campi. Non dobbiamo scrivere nessuna nuova funzione. Avrei potuto evitare totalmente il campo C<_consentito>, ma volevo dimostrare come immagazzinare un riferimento ai dati di una classe nell'oggetto cosE da non dover accedere ai dati della classe direttamente da un metodo dell'oggetto. =head2 Metodi per dati con autoload ereditato Per quanto riguarda l'ereditarietE? Possiamo definire similmente la nostra classe Impiegato? SE, se continuiamo ad aver cautela. Ecco come essere cauti: package Impiegato; use Persona; use strict; our @ISA = qw(Persona); my %campi = ( id => undef, salario => undef, ); sub new { my $classe = shift; my $self = $class->SUPER::new(); my($elemento); foreach $elemento (keys %campi) { $self->{_consentito}->{$elemento} = $campi{$elemento}; } @{$self}{keys %campi} = values %campi; return $self; } Una volta che abbiamo fatto ciE, non abbiamo nemmeno piE bisogno di una funzione AUTOLOAD nel package Impiegato, perchE cattureremo la versione di Persona attraverso l'ereditarietE, e funzionerE tutto alla perfezione. =head1 Strumenti metaclassici Anche se i metodi proxy possono fornire un approccio piE conveniente per rendere le classi piE simili a struct invece che scrivere in modo tedioso metodi per dati come funzioni, lascia sempre un po' a desiderare. Una cosa E che significa dover gestire chiamate senza senso che non intendete catturare tramite il vostro proxy. Significa anche che dovete essere molto cauti nel gestire l'ereditarietE, come spiegato sopra. I programmatori Perl hanno affrontato la questione creando diverse classi per costruire classi. Queste metaclassi sono classi che creano altre classi. Un paio di queste che meritano attenzione sono Class::Struct e Alias. Queste e altre metaclassi collegate si possono trovare nella directory dei moduli su CPAN. =head2 Class::Struct Una delle piE vecchie E Class::Struct. Infatti, la sua sintassi e la sua interfaccia vennero abbozzate molto prima che la versione numero 5 del Perl si materializzasse. Quel che fa E darvi un modo per "dichiarare" una classe per avere oggetti i quali campi siano di un tipo specifico. La funzione che fa ciE si chiama, non abbastanza sorprendentemente, struct(). Dato che le strutture o i record non sono tipi base in Perl, ogni volta che volete creare una classe per fornire oggetti con dati simili a record, voi stessi dovrete definire un metodo new(), e in piE dovrete separare i metodi per l'accesso ai dati per ogni campo di tale record. Vi annoierete presto di questo precedimento. La funzione Class::Struct::struct() allevia questo tedio. Ecco un semplice esempio del suo utilizzo: use Class::Struct qw(struct); use Lavoretto; # definito dall'utente; vedi sotto struct 'FedericE => { uno => '$', molti => '@', professione => 'LavorettE, # non invoca Lavoretto->new() }; $ob = Federico->new(professione => Lavoretto->new()); $ob->uno("hmmmm"); $ob->molti(0, "alla"); $ob->molti(1, "via"); $ob->molti(2, "cosi`"); print "Imposta solo: ", $ob->molti(2), "\n"; $ob->professione->salario(10_000); Potete dichiarare dei tipi nello struct in modo che siano tipi di base del Perl o tipi definiti dall'utente (classi). I tipi dell'utente saranno inizializzati chiamando il metodo new() di quella classe. Osservate che l'oggetto C non viene creato automaticamente dal metodo new() della classe C, quindi dovete specificare un oggetto C quando create un'istanza di C. Ecco un esempio reale di utilizzo con la generazione di stuct. Mettiamo che vogliate sostituire l'idea del Perl di gethostbyname() e gethostbyaddr() in modo che restituiscano oggetti che si comportano come le strutture del C. Non ci interessano dettagli complessi della programmazione orientata agli oggetti. Tutto ciE che vogliamo per questi oggetti E che essi si comportino come struct nell'accezione del C. use Socket; use Net::hostent; $h = gethostbyname("perl.com"); # restituisce l'oggetto printf "Il vero nome di perl.com's e` %s, indirizzo %s\n", $h->name, inet_ntoa($h->addr); Ecco come far ciE utilizzando il modulo Class::Struct. Il nocciolo della questione sarE questa chiamata: struct 'Net::hostent' => [ # osservate le parentesi quadre name => '$', aliases => '@', addrtype => '$', 'length' => '$', addr_list => '@', ]; la quale crea metodi di oggetto con nomi e tipi elencati. Crea inoltre per noi un metodo new(). Avremmo potuto anche implementare il nostro oggetto in questa maniera: struct 'Net::hostent' => { # osservate le parentesi graffe name => '$', aliases => '@', addrtype => '$', 'length' => '$', addr_list => '@', }; in modo che Class::Struct utilizzasse un hash anonimo come tipo di oggetto, anzichE array anonimo. L'array E piE veloce e piE piccolo ma l'hash funziona meglio se ad un certo punto volete implementare l'ereditarietE. Dato che per quest'oggetto simile a uno struct si prevede l'ereditarietE, questa volta opteremo per velocitE e dimensioni migliori a scapito della flessibilitE. Ecco l'intera implementazione: package Net::hostent; use strict; BEGIN { use Exporter (); our @EXPORT = qw(gethostbyname gethostbyaddr gethost); our @EXPORT_OK = qw( $h_name @h_aliases $h_addrtype $h_length @h_addr_list $h_addr ); our %EXPORT_TAGS = ( FIELDS => [ @EXPORT_OK, @EXPORT ] ); } our @EXPORT_OK; # Class::Struct vieta l'utilizzo di @ISA sub import { goto &Exporter::import } use Class::Struct qw(struct); struct 'Net::hostent' => [ name => '$', aliases => '@', addrtype => '$', 'length' => '$', addr_list => '@', ]; sub addr { shift->addr_list->[0] } sub populate (@) { return unless @_; my $hob = new(); # Questo viene fatto da Class::Struct! $h_name = $hob->[0] = $_[0]; @h_aliases = @{ $hob->[1] } = split ' ', $_[1]; $h_addrtype = $hob->[2] = $_[2]; $h_length = $hob->[3] = $_[3]; $h_addr = $_[4]; @h_addr_list = @{ $hob->[4] } = @_[ (4 .. $#_) ]; return $hob; } sub gethostbyname ($) { populate(CORE::gethostbyname(shift)) } sub gethostbyaddr ($;$) { my ($addr, $addrtype); $addr = shift; require Socket unless @_; $addrtype = @_ ? shift : Socket::AF_INET(); populate(CORE::gethostbyaddr($addr, $addrtype)) } sub gethost($) { if ($_[0] =~ /^\d+(?:\.\d+(?:\.\d+(?:\.\d+)?)?)?$/) { require Socket; &gethostbyaddr(Socket::inet_aton(shift)); } else { &gethostbyname; } } 1; Abbiamo intrufolato un bel po' di altri concetti accanto alla semplice creazione dinamica della classe, del tipo sostituzione di funzioni di base, qualche importazione/esportazione, prototipazione di funzioni, scorciatoie a chiamate di funzioni con C<&qualcosa>, e rimpiazzo di funzioni con C. Tutto ciE ha un senso dalla prospettiva dei moduli tradizionali, ma come potete vedere, possiamo anche utilizzarli in un modulo a oggetti. Potete esaminare altre sostituzioni di funzioni di base implementate con oggetti e simili a struct nella release 5.004 del Perl in File::stat, Net::hostent, Net::netent, Net::protoent, Net::servent, Time::gmtime, Time::localtime, User::grent, e User::pwent. Questi moduli hanno un componente finale interamente in minuscolo, per convenzione riservato alle direttive del compilatore, dato che influiscono sulla compilazione e cambiano una funzione integrata. Hanno anche tipi di nomi che un programmatore C si aspetterebbe. =head2 Dati membro come variabili Se avete familiaritE con gli oggetti del C++, allora siete abituati ad accedere ai dati membro di un oggetto come semplici variabili da un metodo. Il modulo Alias provvede a questo e anche a un bel po' di piE, come alla disponibilitE di metodi privati che possono essere invocati dall'oggetto ma non da chi E al di fuori di esso. Ecco un esempio di creazione di una Persona utilizzando il modulo Alias. Quando aggiornate queste magiche variabili di istanza, i valori degli hash vengono aggiornati automaticamente. Conveniente, vero? package Persona; # questo E lo stesso di prima... sub new { my $classe = shift; my $self = { NOME => undef, ETA => undef, COMPAGNI => [], }; bless($self, $classe); return $self; } use Alias qw(attr); our ($NOME, $ETA, $COMPAGNI); sub nome { my $self = attr shift; if (@_) { $NOME = shift; } return $NOME; } sub eta { my $self = attr shift; if (@_) { $ETA = shift; } return $ETA; } sub compagni { my $self = attr shift; if (@_) { @COMPAGNI = @_; } return @COMPAGNI; } sub esclama { my $self = attr shift; return sprintf "Ciao, sono %s, ho %d anni, lavoro con %s", $NOME, $ETA, join(", ", @COMPAGNI); } sub buon_compleanno { my $self = attr shift; return ++$ETA; } L'esigenza della dichiarazione con C deriva dal fatto che Alias manipola variabili globali al package con lo stesso nome dei campi. Per utilizzare variabili globali mentre C E effettivo dovete dichiararle in precedenza. Queste variabili di package sono localizzate nel blocco che racchiude la chiamata ad attr() come se si stia utilizzando un local() su di esse. Ad ogni modo, ciE significa che sono sempre considerate variabili globali con valori temporanei, proprio come ogni altra local(). Sarebbe carino combinare Alias con qualcosa del tipo Class::Struct o Class::MethodMaker. =head1 ANNOTAZIONI =head2 Terminologia degli oggetti Nella varia letteratura sulla programmazione orientata agli oggetti, sembra che siano utilizzate molte parole differenti per descrivere solamente pochi concetti distinti. Se non siete giE un programmatore di oggetti, allora non dovete preoccuparvi di tutte queste parole eleganti. Ma se lo siete, allora potreste voler sapere come ottenere gli stessi concetti in Perl. Per esempio, E comune chiamare un oggetto una I di una classe e chiamare i metodi di questi oggetti I. I campi dati relativi a ogni oggetto sono spesso chiamati I o I, e i campi dati comuni a tutti i membri di una classe sono I, I, oppure I. Inoltre, I, I e I denotano tutti la stessa nozione, mentre I, I e I denotano le altre imparentate. I programmatori C++ hanno I e I, ma il Perl ha solamente I e I. In realtE il Perl ha solo i metodi. Se un metodo viene usato per una classe o per un oggetto E solo a seconda del suo impiego. Potreste accidentalmente chiamare un metodo di classe (che si aspetta un argomento stringa) su un oggetto (che si aspetta un riferimento) o viceversa. Da una prospettiva C++, tutti i metodi del Perl sono virtuali. Questo, a proposito, E il perchE non viene mai effettuata la verifica con i prototipi delle funzioni nella lista degli argomenti mentre ciE si puE fare con funzioni definite dall'utente. Dato che una classe E di per sE qualcosa di un oggetto, le classi del Perl possono essere considerate come rappresentazione sia di una filosofia "classe come meta-oggetto" (chiamata anche I) che di un idea di "classe come definizione di tipo" (comportamento I, non meccanismo I). Il C++ supporta quest'ultima nozione, ma non la precedente. =head1 SI VEDA ANCHE I file di documentazione seguenti forniranno indubbiamente una maggiore conoscenza di base per questo argomento: L, L, L, L, L, e L. L E un'introduzione piE gradevole e piE garbata alla programmazione orientata agli oggetti. L fornisce piE dettagli sui dati di classi. Alcuni moduli che potrebbero risultare interessanti sono Class::Accessor, Class::Class, Class::Contract, Class::Data::Inheritable, Class::MethodMaker e Tie::SecureHash =head1 AUTORE E COPYRIGHT Copyright (c) 1997, 1998 Tom Christiansen Tutti i diritti sono riservati. Questa documentazione E gratuita; puE essere distribuita e/o modificata secondo gli stessi termini del Perl. Indipendentemente dalla sua distribuzione, tutto il codice di esempio in questo documento E di pubblico dominio. Avete il permesso e siete incoraggiati a usare il codice nei vostri programmi per svago o per profitto come ritenete opportuno. Un semplice commento nel codice per citare l'autore sarebbe cortese ma non E richiesto. =head2 Ringraziamenti Grazie a Larry Wall, Roderick Schertler, Gurusamy Sarathy, Dean Roehrich, Raphael Manfredi, Brent Halsey, Greg Bacon, Brad Appleton, e a molti altri per i loro utili commenti. =head1 TRADUZIONE =head2 Versione La versione su cui si basa questa traduzione E ottenibile con: perl -MPOD2::IT -e print_pod perltoot Per maggiori informazioni sul progetto di traduzione in italiano si veda http://pod2it.sourceforge.net/ . =head2 Traduttore Traduzione a cura di Raffaello Galli . =head2 Revisore Revisione a cura di dree.