![]() |
index | project | pods | responsabili |
NOMEperlobj - Gli oggetti in Perl
DESCRIZIONEPrima di tutto bisogna capire cosa sono i riferimenti in Perl. Per fare ciò si consulti perlref. In secondo luogo, se si trova ancora troppo complicata la guida di riferimento che ci si avvia a leggere, si può trovare un tutorial sulla programmazione orientata agli oggetti in Perl in perltoot e perltooc. Se si sta ancora leggendo, qui ci sono tre definizioni molto semplici che si dovrebbero trovare rassicuranti.
Ora si tratteranno questi punti con maggiore profondità.
Un Oggetto è Semplicemente un RiferimentoDiversamente da ciò che accade in C++, Perl non fornisce una sintassi speciale per i costruttori. Un costruttore è solamente una subroutine che restituisce un riferimento a qualcosa di ``blessed'' [letteralmente ``benedetto'', si intende sottoposto alla funzione bless, NdT] in una classe, generalmente la classe nella quale la subroutine è definita. Ecco un tipico costruttore: package Creatura; sub new { bless {} } La parola package Creatura; sub nasci { bless {} } Questo potrebbe persino essere preferibile, poiché i programmatori C++
non saranno indotti a pensare che Una cosa che differisce tra i costruttori in Perl rispetto a
quelli in C++ è, che in Perl, devono allocarsi la propria memoria.
(L'altra cosa è che non chiamano automaticamente i costruttori
della classe base ridefiniti). sub new { my $self = {}; bless $self; return $self; } Spesso si vedono cose del genere in costruttori più complicati che vogliono chiamare metodi della classe come parte della costruzione: sub new { my $self = {}; bless $self; $self->initialize(); # inizializza(), NdR return $self; } Se si ha a cuore l'ereditarietà (e si dovrebbe; si veda perlmodlib/``Modules: Creation, Use, and Abuse'' [``Moduli: Creazione, Uso ed Abuso''>, NdT]) allora si può utilizzare la forma a due argomenti di bless, in modo che i costruttori possano essere ereditati: sub new { my $class = shift; my $self = {}; bless $self, $class; $self->initialize(); return $self; } Oppure, se ci si aspetta che non venga usato solamente
sub new { my $this = shift; my $class = ref($this) || $this; my $self = {}; bless $self, $class; $self->initialize(); return $self; } All'interno dei package della classe, i metodi avranno a che fare in genere con i riferimenti come con i classici riferimenti. Fuori dal package della classe, il riferimento è generalmente trattato come un valore opaco che è accessibile solo attraverso i metodi della classe. Sebbene un costruttore possa in teoria rieffettuare il bless di un oggetto referenziato appartenente in quel momento ad un'altra classe, quasi sicuramente ciò è fonte di guai. La nuova classe ha la responsabilità di chiudere in maniera pulita ogni operazione relativa all'oggetto. Il bless precedente viene dimenticato, poiché un oggetto può appartenere ad una sola classe alla volta. (Anche se, naturalmente, esso è libero di ereditare metodi da molte classi). Ad ogni modo, se ci si trova a doverlo fare, la parent class [``classe genitore'', NdT] avrà probabilmente un comportamento improprio. Un precisazione: gli oggetti in Perl sono sottoposti a bless. I
riferimenti no. Gli oggetti sanno a quale package appartengono. I
riferimenti no. La funzione $a = {}; $b = $a; bless $a, BLABLA; print "\$b is a ", ref($b), "\n"; Questo mette in relazione $b come se fosse un BLABLA, così facendo
ovviamente
Una Classe è Semplicemente un PackageDiversamente dal C++, Perl non fornisce una sintassi speciale per la definizione delle classi. Si usa un package come una classe mettendo le definizioni dei metodi dentro la classe. C'è un array speciale all'interno di ciascun package chiamato @ISA, il quale dice dove altro cercare un metodo se non lo si riesce a trovare nel package corrente. Questo è il modo in cui Perl implementa l'ereditarietà. Ciascun elemento dell'array @ISA è solamente il nome di un altro package che in realtà è un package formante una classe. Le classi vengono cercate (con modalità depth first [ricerca in profondità, NdR]) per i metodi mancanti nell'ordine in cui ricorrono in @ISA. Le classi accessibili attraverso @ISA sono conosciute come classi base della classe corrente. Tutte le classi ereditano implicitamente
dalla classe Se nella classe base viene trovato un metodo mancante, per ragioni di efficienza viene posto in cache nella classe corrente. Cambiare @ISA o definire nuove subroutine invalida la cache e costringe Perl ad una nuova ricerca. Se né la classe corrente, le sue classi base, né la classe UNIVERSAL contengono il metodo richiesto, queste tre posizioni vengono ricontrollate, questa volta cercando un metodo chiamato AUTOLOAD(). Se AUTOLOAD viene trovato, questo metodo viene chiamato in vece del metodo mancante, impostando la variabile di package globale $AUTOLOAD al nome qualificato del metodo destinato ad essere chiamato. Se nulla di tutto ciò funziona, Perl finalmente lascia perdere e protesta. Se si vuole interrompere la catena di ereditarietà di AUTOLOAD si usi semplicemente una sub vuota sub AUTOLOAD; e la chiamata originaria fallirà [die, NdT] lamentandosi di non aver trovato la sub che cercava. Le classi Perl ereditano solamente i metodi. L'ereditarietà dei dati viene lasciata alla classe stessa. Questo in Perl non è un problema, dato che la maggior parte delle classi modellano gli attributi dei loro oggetti utilizzando un hash anonimo, che viene utilizzato come un piccolo namespace locale, al fine di essere spartito dalle varie classi che potrebbero voler far qualcosa con l'oggetto. Il solo problema con tutto ciò, è che non si è per certo sicuri di non stare utilizzando un pezzo di hash che non sia già stato utilizzato. Una soluzione ragionevole è anteporre il nome di chiave nell'hash con il nome del package. sub bump { my $self = shift; $self->{ __PACKAGE__ . ".count"}++; }
Un Metodo è Semplicemente una SubroutineDiversamente dal C++, Perl non fornisce una sintassi speciale per la definizione dei metodi. (Sebbene esso fornisca una piccola sintassi per l'invocazione dei metodi. La si vedrà dopo). Un metodo si aspetta che il suo primo argomento sia un oggetto (riferimento) oppure un package (stringa) sul quale sia stato invocato. Ci sono due strade per chiamare i metodi, che chiameremo ``metodi classe'' e ``metodi istanza''. Un metodo classe si aspetta il nome di una classe come suo primo argomento. Esso fornisce funzionalità per la classe nel suo insieme, non per i singoli oggetti appartenenti alla classe. I costruttori sono spesso metodi classe, ma per delle alternative si consultino perltoot e perltooc. Molti metodi classe ignorano semplicemente il loro primo argomento, perché conoscono già in quale package si trovano e non si curano del package attraverso il quale sono stati invocati. (Questi non sono necessariamente gli stessi, perché i metodi classe seguono l'albero di ereditarietà proprio come un'istanza ordinaria di un metodo). Un altro tipico utilizzo dei metodi classe è cercare un oggetto attraverso il nome: sub find { my ($classe, $nome) = @_; $tabellaoggetti{$nome}; } Un metodo di istanza si aspetta un riferimento ad un oggetto come suo primo argomento. Tipicamente esso trasferisce il primo argomento in una variabile ``self'' o ``this'', e dopo la utilizza come un normale riferimento. sub display { my $self = shift; my @chiavi = @_ ? @_ : sort keys %$self; foreach $chiave (@chiavi) { print "\t$chiave => $self->{$chiave}\n"; } }
Invocazione del Metodo >>Per varie ragioni, storiche e non, Perl offre due modi equivalenti per scrivere una chiamata ad un metodo. Il modo più semplice e comune è quello di utilizzare la notazione con la freccia: my $fred = Creatura->trova("Fred"); $fred->visualizza("Altezza", "Peso"); Si dovrebvbe avere già familiarità con l'utilizzo
dell'operatore Qualsiasi cosa sia sulla sinistra della freccia, un riferimento oppure un nome di una classe, viene passato al metodo come suo primo argomento. Il codice qui sopra è per lo più equivalente a: my $fred = Creatura::trova("Creatura", "Fred"); Creatura::visualizza($fred, "Altezza", "Peso"); Come fa a sapere Perl in quale package si trova la subroutine? Osservando il lato sinistro della freccia, che deve essere o un nome di package o un riferimento ad un oggetto, cioè qualcosa che è stato sottoposto al bless con un package. In entrambi i casi, questo è il package in cui Perl inizia a cercare. Se tale package non ha subroutine con quel nome, Perl inizia a cercarle in tutte le classi di base di quel package, e così via. Se è necessario, è possibile forzare Perl ad iniziare la ricerca in qualche altro package: my $barney = MiaCreatura->Creatura::trova("Barney"); $barney->Creatura::visualizza("Altezza", "Peso"); Qui Come caso particolare di quello precedente, si può utilizzare la
pseudo-classe package MiaCreatura; use base 'Creatura'; # imposta @MiaCreatura::ISA = ('Creatura'); sub visualizza { my ($self, @args) = @_; $self->SUPER::visualizza("Nome", @args); } È importante notare che qualcosa->SUPER::metodo(...); # OK SUPER::metodo(...); # SBAGLIATO SUPER->metodo(...); # SBAGLIATO Invece del nome di una classe o del riferimento ad un oggetto, è anche possibile utilizzare qualsiasi espressione che restituisca ambedue le parti dal lato sinistro della freccia. Dunque, la seguente istruzione è valida: Creatura->trova("Fred")->visualizza("Altezza", "Peso"); ed anche la seguente: my $fred = (reverse "arutaerC")->trova(reverse "derF"); La parte destra della freccia è in genere il nome del metodo, ma può anche essere usata una semplice variabile scalare che contenga o il nome del motodo o un riferimento a subroutine.
Sintassi ad Oggetti IndirettiL'altra strada per invocare un metodo è quella di utilizzare la notazione detta ``indirect object'' [``oggetto indiretto'', NdR]. Questa sintassi era disponibile in Perl 4 prima che fossero introdotti gli oggetti, ed è ancora utilizzata con i filehandle come questo: print STDERR "aiuto!!!\n"; La stessa sintassi può esser utilizzata per chiamare sia un oggetto che un metodo di una classe my $fred = trova Creatura "Fred"; visualizza $fred "Altezza", "Peso"; Si noti che non c'è la virgola tra l'oggetto (o il nome della classe) e i parametri. È in questo modo che Perl può riconoscere che si desiera una chiamata indiretta al metodo piuttosto che una normale invocazione di subroutine. Ma che cosa accade se non ci sono argomenti? In questo caso, Perl deve indovinare quello che si desidera. Ancora peggio, deve tentare questa supposizione a tempo di compilazione. Di solito Perl ci azzecca, ma quando non lo fa, si ottiene una chiamata di funzione scritta come un metodo, o viceversa. Ciò può introdurre dei bug piuttosto difficili da scovare. Ad esempio se c'è già una funzione C'è un altro problema con questa sintassi: l'oggetto indiretto è limitato ad un
nome, ad una variabile scalare, o ad un blocco, visto che, altrimenti, sarebbe costretto a fare
troppe previsioni di ricerca, così come qualsiasi altro dereferenziamento postfisso nel
linguaggio. (Queste sono le stesse bizarre regole che si usano per i filehandle in funzioni come
sposta $ogg->{CAMPO}; # probabilmente erroneo! sposta $ary[$i]; # probabilmente erroneo! Delle quali sorprendentemente viene fatta un'analisi sintattica che porta a: $ogg->sposta->{CAMPO}; # Bene, all'occhio qui $ary->sposta([$i]); # Non ci si aspettava questo, eh? Piuttosto che quello che ci si sarebbe potuto aspettare: $obj->{CAMPO}->sposta(); # Si dovrebbe essere cosi` fortunati. $ary[$i]->sposta; # Si`, certo. Per ottenere un comportamente corretto con la sintassi ad oggetti indiretti, si dovrebbe utilizzare un blocco intorno all'oggetto indiretto: sposta {$ogg->{CAMPO}}; sposta {$ary[$i]}; Addirittura, dunque, si potrebbe avere ancora lo stesso potenziale problema se
ci fosse una funzione chiamata
I Metodi di Default di UNIVERSALIl package
NOTA: È possibile aggiungere altri metodi alla classe UNIVERSAL attraverso Perl o
codice XS. Non c'è bisogno di
DistruttoriQuando viene tolto l'ultimo riferimento ad un oggetto, l'oggetto viene
automaticamente distrutto. (Questo potrebbe accadere anche dopo la chiamata
alla funzione Dato che i metodi DESTROY possono essere chiamati in momenti non
prevedibili a priori, è importante che si localizzino tutte le
variabili globali che il metodo può aggiornare. In particolare,
si localizzi Se si provvede a rifare il bless del riferimento prima che il distruttore termini l'esecuzione, Perl chiamerà nuovamente il metodo DESTROY per l'oggetto risottoposto a bless dopo l'uscita dal metodo DESTROY. Questo può essere utilizzato per delegare in modo pulito la distruzione di oggetti, o per assicurarsi che vengano chiamati i distruttori desiderati nelle classi base. Chiamare esplicitamente DESTROY è possibile, ma di solito non se ne ha bisogno. Non si confonda la discussione precedente con quella su come siano distrutti gli oggetti CONTENUTI nell'oggetto corrente. Tali oggetti saranno rilasciati e distrutti automaticamente quando l'oggetto corrente viene rilasciato, a condizione che non esistano altrove altri riferimenti ad essi.
SommarioQuesto è tutto ciò che c'è da dire. Ora ciò di cui si ha bisogno è andare a comprare un libro sulla metodologia di progettazione orientata agli oggetti, e sbatterci la fronte per i prossimi sei mesi o giù di lì.
Garbage Collection a Due FasiPer la maggior parte degli scopi, Perl utilizza un semplice e veloce sistema di
garbage collection basato su riferimenti. Questo significa che c'è
un ulteriore dereferenziamento che resta in piedi ad un qualche livello, così se
i'eseguibile Perl non è stato compilato utilizzando il flag Una faccenda più seria è che la memoria non raggiungibile con un contatore di riferimenti non a zero, di norma non verrà rilasciata. Pertanto, questa è una cattiva idea: { my $a; $a = \$a; } Anche se si pensa che $a dovrebbe sparire, non può. Quando si costruiscono strutture dati ricorsive, si dovrà rompere l'auto-referenzialità a mano, esplicitamente, a meno che non si voglia sprecare memoria. Per esempio, ecco un nodo che si auto-referenzia come si può utilizzare in una sofisticata struttura ad albero: sub nuovo_nodo { my $classe = shift; my $nodo = {}; $nodo->{SINISTRA} = $node->{DESTRA} = $nodo; $nodo->{DATO} = [ @_ ]; return bless $nodo => $classe; } Se si creano nodi come questi, essi (al momento) non spariranno a meno che non si interrompa il loro auto riferimento. (In altre parole, questo non dovrebbe essere interpretato come una peculiarità, e non si dovrebbe farne affidamento). Quasi. Quando un thread dell'interprete alla fine termina (di solito quando il programma conclude l'esecuzione), allora viene eseguita una tipologia di garbage collection mark-and-sweep [``segna e pulisci'' NdT] un po' costosa ma completa, e tutto ciò che è stato allocato da quel thread viene distrutto. Ciò è essenziale per supportare un linguaggio multithread come Perl. Per esempio, questo programma mostra la garbage collection a due fasi di Perl: #!/usr/bin/perl package Ingegnoso; sub new { my $test; $test = \$test; warn "CREAZIONE DI " . \$test; return bless \$test; } sub DESTROY { my $self = shift; warn "DISTRUZIONE DI $self"; } package main; warn "avvio del programma"; { my $a = Ingegnoso->new; my $b = Ingegnoso->new; $$a = 0; # rompe l'autoreferenzialita` warn "sto lasciando il blocco"; } warn "sono appena uscito dal blocco"; warn "e` tempo di morire..."; exit; Se eseguiamo lo script come /foo/test, viene prodotto il seguente output: avvio del programma at /foo/test line 18. CREAZIONE DI SCALAR(0x8e5b8) at /foo/test line 7. CREAZIONE DI SCALAR(0x8e57c) at /foo/test line 7. sto lasciando il blocco at /foo/test line 23. DISTRUZIONE DI Ingegnoso=SCALAR(0x8e5b8) at /foo/test line 13. sono appena uscito dal blocco at /foo/test line 26. e` tempo di morire... at /foo/test line 27. DISTRUZIONE DI Ingegnoso=SCALAR(0x8e57c) during global destruction. [durante la distruzione globale, NdR] Notate la riga che parla di ``global destruction''? Quello è il thread del garbage collection che sta raggiungendo l'irraggiungibile. Gli oggetti sono sempre distrutti, anche quando i riferimenti non lo sono. Gli oggetti sono
distrutti in un passaggio separato, prima dei riferimenti comuni, proprio per prevenire che il
distruttore degli oggetti utilizzi dei riferimenti che sono stati a loro volta distrutti.
I riferimenti comuni vengono sottoposti al garbage-collector se il ``destruct level''
[``livello di distruzione, NdR] è maggiore di 0. È possibile esaminare i livelli
più elevati di ''global destruction`` andando ad impostare la variabile d'ambiente
PERL_DESTRUCT_LEVEL, con l'assunzione che sia stato abilitato In futuro sarà implementata una strategia di garbage collection più completa. Nel frattempo, la soluzione migliore è quella di creare delle classi contenitore non ricorsive che mantengano un puntatore alla struttura dati auto referenziale. Definite un metodo DESTROY per gli oggetti della classe contenitore che vada manualmente a rompere le circolarità nella struttura auto referenziale.
SI VEDA ANCHESi possono tovare dei tutorial più semplici sulla programmazione orientata agli oggetti in Perl in perltoot, perlboot e perltooc. Per altri trucchi, trappole e suggerimenti sugli oggetti si dovrebbe dare una controllata a perlbot, così come perlmodlib per alcune linee guida stilistiche sulla costruzione sia di moduli che di classi.
TRADUZIONE
VersioneLa versione su cui si basa questa traduzione è ottenibile con: perl -MPOD2::IT -e print_pod perlobj Per maggiori informazioni sul progetto di traduzione in italiano si veda http://pod2it.sourceforge.net/ .
TraduttoreTraduzione a cura di Daniele Ludovici.
RevisoreRevisione a cura di dree, dada. Mon Jun 11 22:02:17 2012 |