index | project | pods | responsabili

NAME

perlobj - Gli oggetti in Perl


DESCRIPTION

Per prima cosa, avete bisogno di capire che cosa sono i referimenti in Perl. Consultate perlref per questo. In secondo luogo, se trovate ancora troppo complicato il funzionamento(?) dei riferimenti che segue, potete trovare un tutorial sulla programmazione orientata agli oggetti in Perl in perltoot e perltooc.

Se siete ancora con noi (? se ci state ancora seguendo), allora qui ci sono tre semplici definizioni che dovreste trovare rassicuranti.

  1. Un oggetto è semplicemente un riferimento che in realtà sa a quale classe esso appartiene.

  2. Una classe è semplicemente un package che fornisce metodi per dialogare con riferimenti ad oggetti.

  3. Un metodo è semplicemente una subroutine che si aspetta il riferimento ad un oggetto (o un nome di package, per i metodi classe) come primo argomento.

Ora tratteremo esaurientemente questi punti in maggiore profondità.

Un Oggetto è Semplicemente un Riferimento

Diversamente 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, N.d.T.] nella classe, generalmente la classe nella quale la subroutine è definita. Questo è un tipico costruttore:

    package Critter;
    sub new { bless {} }

La parola new non è speciale. Potevate aver scritto il costruttore anche in questo modo:

    package Critter;
    sub spawn { bless {} }

Questo potrebbe persino esser preferibile, poiché i programmatori C++ non saranno imbrogliati(?) nel credere che new funzioni in Perl come fa in C++. Non lo fa. Vi raccomandiamo di dare un nome ai vostri costruttori con qualsiasi cosa abbia senso nel contesto del problema che state risolvendo. Per esempio, ai costruttori dell'estensione Tk del Perl viene dato un nome dopo dei widget che hanno creato(?).

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 sovrascritta(?)). {} alloca un hash anonimo che non contiene coppie chiave/valore, e lo restituisce. bless() prende quel riferimento e dice all'oggetto che esso referenzia, che ora è un Critter [``un essere vivente'' N.d.T.] e restituisce il riferimento. Questo è fatto per convenienza, perché l'oggetto riferito stesso sa di essere stato sottoposto a bless, e il riferimento ad esso potrebbe esser stato restituito direttamente, come in questo caso:

    sub new {
        my $self = {};
        bless $self;
        return $self;
    }

Spesso vedete una cosa come questa in costruttori più complicati che vogliono chiamare metodi nella classe come parte della costruzione:

    sub new {
        my $self = {};
        bless $self;
        $self->initialize();
        return $self;
    }

Se vi preoccupate dell'ereditarietà (e dovreste; si veda perlmodlib/``Modules: Creation, Use, and Abuse'' [``Moduli: Creazione, Uso ed Abuso''>, N.d.T.]) allora potete utilizzare la forma a due argomenti di bless così che i vostri costruttori possano essere ereditati:

    sub new {
        my $class = shift;
        my $self = {};
        bless $self, $class;
        $self->initialize();
        return $self;
    }

Oppure se vi aspettate che le persone non chiamino solamente <CLASS-new() >> ma anche $obj->new(), allora utilizzate qualcosa come la seguente. (Va notato che utilizzare questo modo per chiamare new() in un'istanza, non fa automaticamente una copia. Se volete una copia superficiale [``shallow'', copia del solo oggetto, N.d.T.] o in profondità [``deep'', copia dell'oggetto e di quanto da lui riferito, N.d.T.] di un oggetto, allora dovretepermetterlo specificatamente). Il metodo initialize() utilizzato, sarà di una qualsiasi classe $class nella quale abbiamo sottoposto l'oggetto a bless:

    sub new {
        my $this = shift;
        my $class = ref($this) || $this;
        my $self = {};
        bless $self, $class;
        $self->initialize();
        return $self;
    }

Nei package della classe, i metodi comunicheranno tipicamente con i riferimenti come riferimenti classici. Fuori dal package della classe, il riferimento è generalmente trattato come un valore opaco che potrebbe esser acceduto solo attraverso i metodi della classe.

Sebbene un costruttore possa in teoria re-bless un oggetto riferito correntemente che appartiene ad un'altra classe, quasi sicuramente ciò vi porterà in confusione. La nuova classe è responsabile per tutte le pulizie successive. Il blessing precedente è dimenticato, come se un oggetto potesse appartenere ad una sola classe alla volta. (Sebbene naturalmente esso sia libero di ereditare metodi da molte classi). Se vi trovate a doverlo fare, la parent class [``classe genitore'', N.d.T.] si sta probabilmente comportando male.

Un chiarificazione: gli oggetti in Perl sono sottoposti a bless. I riferimenti no. Gli oggetti sanno a quale package appartengono. I riferimenti no. La funzione bless() usa il riferimento per trovare l'oggetto. Considerate il seguente esempio:

    $a = {};
    $b = $a;
    bless $a, BLAH;
    print "\$b is a ", ref($b), "\n";

Questo mette in relazione $b come se fosse un BLAH, così facendo ovviamente bless() ha operato sull'oggetto e non sul riferimento.

Una classe è Semplicemente un Package

Diversamente dal C++, Perl non fornisce una sintassi speciale per la definizione delle classi. Potete utilizzare 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 guardare per un metodo se non viene trovato 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 class package. Le classi sono cercate (prima dal fondo) per metodi non trovati nell'ordine in cui sono in @ISA. Le classi accessibili attraverso @ISA sono conosciute come classi base della classe corrente. Tutte le classi implicitamente ereditano dalla classe UNIVERSAL come loro ultima classe base. Alcuni metodi comunemente utilizzati sono automaticamente forniti nella classe UNIVERSAL; consultate Default UNIVERSAL methods [``I metodi UNIVERSAL di default'', N.d.T.] per maggiori dettagli.

Se viene trovato un metodo mancande nella classe base, esso viene posto in cache nella classe corrente per efficienza. Cambiando @ISA o definendo nuove subroutine invalida la cache e costringe Perl ad una nuova ricerca.

Se né la classe corrente, la sua classe base con nome, né la classe UNIVERSAL contengono il metodo richiesto, questi tre posti vengono ricontrollati, questa volta guardando per un metodo chiamato AUTOLOAD(). Se viene trovato AUTOLOAD, questo metodo viene chiamato in vece del metodo mancante, impostando la variabile package globale $AUTOLOAD per essere un nome completamente qualificato per il metodo che si intendeva chiamare.

Se nulla di questo funziona, Perl finalmente lascia perdere e protesta.

Se volete interrompere la catena di ereditarietà di AUTOLOAD mettete semplicemente una sub vuota

        sub AUTOLOAD;

e la chiamata originaria fallirà (die) lamentandosi di non aver trovato la sub che cercava.

Le classi Perl fanno solamente ereditarietà di metodo. L'ereditarietà dati è lasciata alla classe stessa. Questo non è un problema in Perl, dato che molte classi modellano gli attributi dei loro oggetti utilizzando un hash anonimo, che fa da piccolo namespace proprio per essere spartito dalle varie classi che potrebbero voler far qualcosa cone l'oggetto. Il solo problema con tutto ciò, è che non potete esser sicuri di non stare utilizzando un pezzo di hash che non sia già stato utilizzato. Una soluzione ragionevole è prependere il nome del package al nome del campo considerato nell'hash.

    sub bump {
        my $self = shift;
        $self->{ __PACKAGE__ . ".count"}++;
    }

Un Metodo è Semplicemente una Subroutine

Diversamente 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 vedremo dopo). Un metodo si aspetta che il suo primo argomento sia un oggetto (riferimento) oppure un package (stringa) sul quale è stato invocato. Ci sono due strade per chiamare i metodi, che chiameremo ``metodi classe'' e ``metodi instanza''.

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 consultate perltoot e perltooc per delle alternative. Molti metodi classe ignorano semplicemente il loro primo argomento, perché essi già conoscono in quale package sono 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à giusto come un'istanza ordinaria di un metodo). Un altro tipico utilizzo dei metodi classe è cercare un oggetto attraverso il nome:

    sub find {
        my ($class, $name) = @_;
        $objtable{$name};
    }

Un metodo 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 riferimento classico.

    sub display {
        my $self = shift;
        my @keys = @_ ? @_ : sort keys %$self;
        foreach $key (@keys) {
            print "\t$key => $self->{$key}\n";
        }
    }

Invocazione del Metodo >>

Per varie ragioni storiche e non, Perl offre due strade equivalenti per scrivere una chiamata di metodo. Il modo più semplice e comune è utilizzare la notazione con la freccia:

    my $fred = Critter->find("Fred");
    $fred->display("Height", "Weight");

Dovreste avere già familiarità con l'utilizzo dell'operatore -> con i riferimenti. In fatti dato che $fred sopra è un riferimento ad un oggetto, potreste pensare alla chiamata di metodo semplicemente come un'altra forma di dereferenziazione.

Qualsiasi cosa sulla sinistra della freccia, sia un riferimento o un nome di classe, viene passato al metodo come suo primo argomento. Il codice sopra è generalmente equivalente a:

    my $fred = Critter::find("Critter", "Fred");
    Critter::display($fred, "Height", "Weight");

Come fa a sapere Perl in quale package si trova la subroutine? Guardando al lato sinistro della freccia, il quale deve esser sia un nome di package o un riferimento ad un oggetto, cioè qualcosa che è stato blessed ad un package. In entrambi i casi, questo è il package nel quale Perl comincia a cercare. Se quel package non ha subroutine con quel nome, Perl comincia a cercarlo in qualsiasi classe base di quel package, e così via.

Se ne avete bisogno, potete forzare Perl a cominciare la ricerca in qualche altro package:

    my $barney = MyCritter->Critter::find("Barney");
    $barney->Critter::display("Height", "Weight");

Qui MyCritter è presumibilmente una sottoclasse di Critter che definisce una sua versione di find() e display(). Non abbiamo specificato cosa questi metodi facciano, ma ciò non ha importanza dato che abbiamo forzato Perl a cominciare la ricerca delle subroutines in Critter.

Come caso speciale di quello precedente, potreste utilizzare la pseudo-classe SUPER per dire a Perl di cominciare la ricerca dei metodi nel package nell'attuale lista delle classi @ISA.

    package MyCritter;
    use base 'Critter';    # imposta @MyCritter::ISA = ('Critter');
    sub display { 
        my ($self, @args) = @_;
        $self->SUPER::display("Name", @args);
    }

È importante notare che SUPER si riferisce alla/alle superclasse/i del package corrente e non alla/alle superclasse/i dell'oggetto. Inoltre la pseudo-classe SUPER può attualmente essere utilizzata solamente come modificatrice del nome di un metodo, ma non in tutti gli altri modi con i quali i nomi delle classi sono normalmente utilizzati, ad esempio:

    something->SUPER::method(...);      # OK
    SUPER::method(...);                 # SBAGLIATO
    SUPER->method(...);                 # SBAGLIATO

Invece del nome di una classe o del riferimento ad un oggetto, potete anche utilizzare qualunque espressione che restituisce ambedue le parti sul lato sinistro della freccia. Dunque, la seguente istruzione è valida:

    Critter->find("Fred")->display("Height", "Weight");

ed anche la seguente seguente:

    my $fred = (reverse "rettirC")->find(reverse "derF");

La parte destra della freccia è tipicamente il nome del metodo, ma una variabile scalare semplice che contenga o il nome del motodo o un riferimento a subroutine può anche esser utilizzato

Sintassi indiretta degli Oggetti

L'altra strada per invocare un metodo è utilizzando la notazione detta ``indirect object''. Questa sintassi era disponibile in Perl 4 prima che gli oggetti fossero introdotti, ed è ancora utilizzata con i filehandles come questo:

   print STDERR "aiuto!!!\n";

La stessa sintassi può esser utilizzata per chiamare sia un oggetto che un metodo classe

   my $fred = find Critter "Fred";
   display $fred "Height", "Weight";

Notate che non c'è la virgola tra l'oggetto (o il nome della classe) e i parametri. è in questo modo che Perl si accorge che volete una chiamata indiretta a metodo piuttosto che una normale invocazione di subroutine.

Ma che cosa accade se non ci sono argomenti? In quel caso Perl deve supporre quello che voi volete. Peggio ancora, deve tirare ad indovinare durante la compilazione. Normalmente Perl ci indovina, ma quando negli altri casi vi ritrovate una chiamata di funzione compilata come invocazione di metodo, o viceversa. Ciò può introdurre dei bug piuttosto difficili da scovare.

Per esempio, una chiamata al metodo new in notazione indiretta --come i programmatori C++ sono abituati a fare -- potrebbe esser mal compilata dentro una chiamata di subroutine se ci fosse già una funzione new nello scope. Dovreste terminarla chiamando la new del package corrente come una subroutine, anziché il metodo della classe desiderata. Il compilatore cerca di imbrogliare ricordandosi la parola require, ma la pena quando fa confusione non vale il pregio degli anni di debugging che ci mettereste a scovare tali subdoli bug.

C'è un altro problema con questa sintassi: l'oggetto indiretto è limitato al nome, ad una variabile scalare, o un blocco, perché altrimenti avrebbe da fare troppe ricerche, così come qualsiasi altro dereferenziamento postfisso nel linguaggio. (Sono le stesse bizarre regole come si usano per i filehandle in funzioni come print e printf.) Ciò può indurre ad orribili problemi di confusione nella precedenza, proprio come nelle prossime due linee:

    move $obj->{FIELD};                 # probabilmente sbagliato!
    move $ary[$i];                      # probabilmente sbagliato!

Vengono attualmente parsate [``analizzate'' N.d.T.] con molta sorpresa:

    $obj->move->{FIELD};                # Bene, guarda qui
    $ary->move([$i]);                   # Non vi aspettavate questo, eh?

Anziché quello che avreste previsto:

    $obj->{FIELD}->move();              # Dovreste esser cosi` fortunati.
    $ary[$i]->move;                     # Yeah, sicuro.

Per ottenere un comportamente corretto con la sintassi ``indirect object'', dovreste utilizzare un blocco intorno all'oggetto indiretto:

    move {$obj->{FIELD}};
    move {$ary[$i]};

Addirittura quindi, potreste ancora avere lo stesso potenziale problema se ci fosse una funzione chiamata move nel package corrente. la -> notation non soffre di queste ambiguità che possono dar fastidio, quindi vi raccomandiamo di usarla esclusivamente. Comunque, potreste ancora dover leggere codice che utilizza la notazione ad oggetti indiretti, quindi è importante familiarizzare con essa.

Metodi di default UNIVERSAL

Il package UNIVERSAL contiene automaticamente i seguenti metodi che sono ereditati da tutte le altre classi:

isa(CLASS)
isa restituisce true [``vero'' N.d.T.] se il suo oggetto è stato blessed dentro una sottoclasse di CLASS.

Potete anche chiamare UNIVERSAL::isa come un subroutine con due argomenti. Naturalmente questo fare la cosa sbagliata se qualcosa ha ignorato ISA nella classe, quindi non fatelo.

Se avete bisogno di determinare se avete ricevuto un'invocazione valida, utilizzate la funzione blessed da the Scalar::Util manpage:

    if (blessed($ref) && $ref->isa( 'Some::Class')) {
        # ...
    }

blessed restituisce il nome del package dentro il quale l'argomento è stato blessed, oppure undef.

can(METODO)
can controlla se il suo oggetto abbia un metodo chiamato METODO, se è così, allora viene restituito un riferimento alla subroutines, altrimenti viene restituito undef.

UNIVERSAL::can può anche esser chiamata come un subroutine con due argomenti. Essa restituisce sempre undef se il suo primo argomento non è un oggetto o il nome di una classe. Inoltre, qui viene direttamente applicato lo stesso avvertimento per la chiamata di UNIVERSAL::isa.

VERSION( [NEED] )
VERSION restituisce il numero di versione della classe (package). Se viene fornito l'argomento NEED allora essa controllerà che la versione corrente (come definita dalla variabile $VERSION nel package dato) non sia minore di NEED; essa terminerà se così non fosse. Questo metodo è normalmente chiamato come metodo classe. Questo metodo è chiamato automaticamente dalla forma VERSION di use.
    use A 1.2 qw(alcune funzioni importate);
    # implica:
    A->VERSION(1.2);

Potreste aggiungere altri metodi alla classe UNIVERSAL attraverso Perl o codice XS. Non dovreste aver bisogno di use UNIVERSAL per render questi metodi disponibili al vostro programma (e non dovreste farlo).

Distruttori

Quando l'ultimo riferimento ad un oggetto va via, l'oggetto viene automaticamente distrutto. (Questo potrebbe accadere dopo la terminazione del programma, se avete salvato un riferimento in qualche variabile globale.) Se volete avere il controllo di ciò che accade appena prima che l'oggetto sia liberato, allora potreste definire un metodo DESTROY nella vostra classe. Esso sarà chiamato automaticamente al momento appropriato, e voi potrete fare le ulteriori pulizie di cui avete bisogno. Perl passa (al metodo DESTROY) un riferimento all'oggetto che sta per esser distrutto come primo (ed unico) argomento. Siate sicuri che il riferimento sia un valore read-only [``accedibile in sola lettura'' N.d.T.], e che non possa esser modificato manipolando $_[0] all'interno del distruttore. L'oggetto stesso (i.e. la cosa a cui il riferimento punta, cioè ${$_[0]}, @{$_[0]}, %{$_[0]} etc.) non ha di questi vincoli.

Dato che i metodi DESTROY possono esser chiamati in tempi non predicibili a priori, è importante che voi localizziate qualsiasi variabile globale che il metodo potrebbe aggiornare. In particolare, localizzate $@ se utilizzate eval {} e localizzate $? se utilizzate system o i backticks [``gli apici inversi ` ` che vengono utilizzati per l'esecuzione di un comando esterno'', N.d.T.]

Se vi arrangiate a re-blessare il riferimento prima che il distruttore ritorni, Perl chiamerà ancora il metodo DESTROY per l'oggetto re-blessed dopo che quello corrente ritorna. Questo può esser utilizzato per delegare in modo pulito la distruzione di oggetti, o per assicurarsi che i distruttori nelle classi base di vostra scelta siano chiamati. Chiamare esplicitamente DESTROY è possibile, ma di solito non se ne ha bisogno.

Non confondete la discussione precedente con quella sul come gli oggetti CONTAINED siano distrutti nell'oggetto corrente. Tali oggetti saranno liberati e distrutti automaticamente quando l'oggetto corrente viene liberato, non fornendo altri riferimenti a loro esisteranno da qualche altra parte.

Sommario

Questo è tutto. Ora ciò di cui avete bisogno è comprare un libro sulla metodologia di progettazione object-oriented, e far esplodere la vostra testa con esso nei prossimi sei mesi o più.

Garbage Collection a Due Fasi

Per mosti scopi, Perl utilizza un veloce e semplice, sistema di garbage collection basato su riferimenti. Questo significa che c'è un altro dereferenziamento che avviene a qualche livello, così se non avete compilato il vostro eseguibile Perl utilizzando il flag del del compilatore C -O, le performance ne soffriranno. Se avete compilato Perl con cc -O, allora questo non vi importa.

Una faccenda più seria è che la memoria irraggiungibile con un contatore di riferimenti non a zero non verrà liberata normalmente. Quindi questa è una cattiva idea:

    {
        my $a;
        $a = \$a;
    }

Anche se $a dovrebbe sparire, non può. Quando avete costruito strutture dati ricorsive, dovrete spezzare i cicli di riferimenti a mano, altrimenti la memoria non sarà liberata. Per esempio, qui c'è un nodo che si auto-referenzia così che uno lo potrebbe utilizzare in una sofisticata struttura ad albero:

    sub new_node {
        my $class = shift;
        my $node  = {};
        $node->{LEFT} = $node->{RIGHT} = $node;
        $node->{DATA} = [ @_ ];
        return bless $node => $class;
    }

Se create nodi come questi, essi (correntemente) non dovrebbero sparire senza che voi spezziate il loro auto riferimento. (In altro parole, questo non dovrebbe esser interpretato come una caratteristica, e voi non dovreste farvi affidamento).

Quasi.

Quando un thread dell'interprete finalmente si spegne (solitamente quando il vostro programma esce), allora viene fatto un abbastanza costoso ma completo mark-and-sweep [``segna e pulisci'' N.d.T.] stile di garbage collection, e qualsiasi cosa allocata da quel thread viene distrutta. Questo è essenziale per supportare un linguaggio multithread come Perl. Per esempio, questo programma dimostra la garbage collection a due fasi di Perl:

    #!/usr/bin/perl
    package Subtle;
    sub new {
        my $test;
        $test = \$test;
        warn "CREATING " . \$test;
        return bless \$test;
    }
    sub DESTROY {
        my $self = shift;
        warn "DESTROYING $self";
    }
    package main;
    warn "starting program";
    {
        my $a = Subtle->new;
        my $b = Subtle->new;
        $$a = 0;  # break selfref
        warn "leaving block";
    }
    warn "just exited block";
    warn "time to die...";
    exit;

Quando fate girare /foo/test, viene prodotto il seguente output:

    starting program at /foo/test line 18.
    CREATING SCALAR(0x8e5b8) at /foo/test line 7.
    CREATING SCALAR(0x8e57c) at /foo/test line 7.
    leaving block at /foo/test line 23.
    DESTROYING Subtle=SCALAR(0x8e5b8) at /foo/test line 13.
    just exited block at /foo/test line 26.
    time to die... at /foo/test line 27.
    DESTROYING Subtle=SCALAR(0x8e57c) during global destruction.

Notate quel ``global destruction''? Quello è il thread della garbage collection che sta raggiungendo l'irraggiungibile.

Gli oggetti sono sempre distrutti, anche quando non ci sono riferimenti regolari. Gli oggetti sono distrutti in passi separati, prima i riferimenti ordinari così da prevenire che il distruttore di oggetti utilizzi dei riferimenti che sono stati essistessi già distrutti. I riferimenti normali sono garbage-collected se il ``destruct level'' è maggiore di 0. Potete testare i livelli maggiori di ``global destruction'' andando a settare la variabile d'ambiente PERL_DESTRUCT_LEVEL, presumendo che -DDEBUGGING fosse abilitato quando Perl è stato compilato. Vedete perlhack/PERL_DESTRUCT_LEVEL per maggiori informazioni.

In futuro sarà implementata una strategia di garbage collection più completa.

Nel frattempo, la miglior soluzione è creare delle classi container non ricorsive che mantengano un puntatore alla struttura dati self-referential. Definire un metodo DESTROY per gli oggetti ``containing'' della classe che manualmente andranno a rompere le circolarità nella struttura self-referential.


SI VEDA ANCHE

Un tutorial più leggero sulla programmazione orientata agli oggetti in Perl può esser trovato in perltoot, perlboot e perltooc. Dovreste anche controllare perlbot per altri trucchi, trappole e suggerimenti sugli oggetti, così come perlmodlib per delle ragguardevoli linee guida sulla costruzione di moduli e classi.


TRADUZIONE

Versione

La 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/ .

Traduttore

Traduzione a cura di Daniele Ludovici.

Revisore

Revisione a cura di .


Wed Jan 28 14:17:56 2009