index | project | pods | responsabili


NOME

perlobj - Guida di riferimento agli oggetti in Perl


DESCRIZIONE

Questo documento fornisce una guida di riferimento per le caratteristiche del Perl nell'orientamento agli oggetti. Se state cercando un'introduzione alla programmazione orientata agli oggetti in Perl, si veda perlootut.

Al fine di comprendere gli oggetti in Perl, è necessario prima capire i riferimenti in Perl. Per i dettagli si veda perlref.

Questo documento descrive tutte le caratteristiche orientate agli oggetti (OO) del Perl, proprio dalle basi. Se state solo cercando di scrivere un po' di codice orientato agli oggetti, vi sarà più utile utilizzare uno dei sistemi ad oggetti che si trovano su CPAN, descritti in perlootut.

Se state cercando di scrivere il vostro sistema ad oggetti, oppure è necessario manutenere del codice che implementa gli oggetti da zero allora questo documento vi aiuterà a capire esattamente come funziona il Perl orientato agli oggetti.

Ci sono alcuni principi fondamentali che definiscono il Perl orientato agli oggetti:

  1. Un oggetto è semplicemente una struttura dati che sa a quale classe appartiene.

  2. Una classe è semplicemente un package. Una classe fornisce i metodi che si aspettano di operare sugli oggetti.

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

Diamo un'occhiata in dettaglio a ciascuno di questi principi.

Un Oggetto è Semplicemente una Struttura Dati

A differenza di molti altri linguaggi che supportano l'orientamento agli oggetti, Perl non fornisce una sintassi speciale per la costruzione di un oggetto. Gli oggetti sono solo strutture dati Perl (array, hash, scalari, filehandle, ecc) che sono state esplicitamente associate ad una particolare classe.

Tale esplicita associazione viene creata dalla funzione integrata bless, che di norma è usata nella subroutine costruttore della classe.

Ecco un semplice costruttore:

  package File;
  sub new {
      my $classe = shift;
      return bless {}, $classe;
  }

Il nome new non è speciale. Potremmo dare al nostro costruttore un altro nome:

  package File;
  sub carica {
      my $classe = shift;
      return bless {}, $classr;
  }

La moderna convenzione per i moduli OO è di usare sempre new come il nome del costruttore, ma non vi è alcun obbligo nel farlo. In Perl, qualsiasi subroutine che effettua il bless di una struttura di dati in una classe è un costruttore valido.

Negli esempi precedenti, il codice {} crea un riferimento ad un hash anonimo vuoto. Poi la funzione bless prende quel riferimento e associa l'hash con la classe $classe. Nel caso più semplice, la variabile $classe finirà con contenere la stringa ``File''.

Si può anche utilizzare una variabile per memorizzare un riferimento alla struttura dati che è stata sottoposta a bless quale nostro oggetto:

  sub new {
      my $classe = shift;
      my $self = {};
      bless $self, $classe;
      return $self;
  }

Una volta che abbiamo sottoposto l'hash a bless, hash a cui si fa riferimente tramite $self, possiamo iniziare a chiamare dei metodi proprio tramite $self. Questo è utile se si vuole porre l'inizializzazione dell'oggetto in un proprio metodo separato:

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

Poiché l'oggetto è anche un hash, è possibile trattarlo come un hash, utilizzarlo per memorizzare i dati associati con l'oggetto. In genere, il codice all'interno della classe può trattare l'hash come una struttura dati accessibile, mentre il codice al di fuori della classe deve sempre trattare l'oggetto come non trasparente. Ciò è chiamato incapsulamento. Incapsulamento significa che l'utente di un oggetto fa come se non fosse necessario sapere come l'oggetto viene implementato. L'utente chiama semplicemente i metodi documentati dell'oggetto.

Si noti, tuttavia, che (a differenza di molti altri linguaggi OO) Perl non garantisce o applica in alcun modo l'incapsulamento. Se si desidera che gli oggetti siano in realtà non trasparenti è necessario provvedere alla cosa da soli. Questo può essere fatto in una varietà di modi, incluso l'utilizzo degli oggetti Inside-Out o moduli da CPAN.

Gli Oggetti Sono Sottoposti A Bless; Non Le Variabili

Quando usiamo la funzione bless su qualcosa, non stiamo sottoponendo a bless la variabile che contiene un riferimento a quella cosa, né stiamo sottoponendo a bless il riferimento che la variabile contiene; stiamo sottoponendo a bless la cosa a cui la variabile fa riferimento (conosciuto anche come il referente). Ciò si dimostra meglio con questo codice:

  use Scalar::Util 'blessed';
  my $pippo = {};
  my $pluto = $pippo;
  bless $pippo, 'Classe';
  print blessed( $pluto );      # stampa "Classe"
  $pluto = "un altro valore";
  print blessed( $pluto );      # stampa undef

Quando chiamiamo bless su una variabile, in realtà stiamo sottoponendo a bless la sottostante struttura dati a cui la variabile fa riferimento. Non sottoponiamo a bless il riferimento stesso, né la variabile che contiene il riferimento. Ecco perché la seconda chiamata a blessed($pluto) restituisce falso. A quel punto $pluto non sta più memorizzando un riferimento ad un oggetto.

A volte si vedono vecchi libri o della documentazione menzionare ``sottoporre a bless un riferimento'' o descrivere un oggetto come ``riferimento sottoposto a bless'', ma questo è errato. Non è il riferimento che è sottoposto a bless come oggetto: è la cosa a cui il riferimento si riferisce (cioè il referente).

Una Classe è Semplicemente un Package >

Perl non fornisce una sintassi speciale per le definizioni di classe. Un package è semplicemente un namespace contenente variabili e subroutine. L'unica differenza è che in una classe, i sottoprogrammi si possono aspettare un riferimento ad un oggetto o il nome di una classe come primo argomento. Ciò è puramente una questione di convenzione, quindi una classe può contenere sia metodi sia subroutine che non operano su un oggetto o una classe.

Ogni package contiene uno speciale array chiamato @ISA. L'array @ISA contiene un elenco di classi genitore di quella classe, se ne esistono. Questo array viene esaminato quando Perl esegue la risoluzione del metodo, che tratteremo in seguito.

È possibile impostare manualmente @ISA, e ciò si può vedere in vecchio codice Perl. Codice molto più vecchio utilizza anche la direttiva base. Per del codice moderno, si consiglia di utilizzare la direttiva parent per dichiarare i propri genitori. Questa direttiva si prenderà cura di impostare @ISA. Caricherà inoltre le classi genitore e si assicurerà che il package non erediti da se stesso.

Comunque le classi genitore siano impostate, la variabile @ISA del package conterrà un elenco di quei genitori. Questa è semplicemente una lista di scalari, ognuna delle quali è una stringa che corrisponde al nome del package.

Tutte le classi ereditano implicitamente dalla classe UNIVERSAL. La classe UNIVERSAL viene implementata dalle funzioni core [appartenente ai moduli base, presenti da subito nell'installazione perl, NdT] del Perl, e fornisce diversi metodi predefiniti, come ad esempio isa(), can() e VERSION(). La classe UNIVERSAL non apparirà mai nella variabile @ISA di un package.

Perl fornisce solo metodi ereditati come funzionalità incorporate????. L'attributo dell'ereditarietà viene lasciato da implementare alla classe. Per maggiori dettagli si veda Scrivere Accessor.

Un Metodo è Semplicemente una Subroutine

Perl non fornisce una sintassi speciale per la definizione di un metodo. Un metodo è semplicemente una normale subroutine, e viene dichiarata con sub. Ciò che rende speciale un metodo è che si aspetta di ricevere un oggetto o un nome di una classe come primo argomento.

Perl fornisce una sintassi speciale per l'invocazione di un metodo, l'operatore ->. Ci occuperemo di maggiori dettagli in seguito.

La maggior parte dei metodi che scrivete si aspetta di operare su oggetti:

  sub salva {
      my $self = shift;
      open my $fh, '>', $self->percorso() or die $!;
      print {$fh} $self->dati()           or die $!;
      close $fh                           or die $!;
  }

Invocazione dei Metodi >>

Chiamare un metodo su un oggetto viene scritto come $oggetto->metodo.

Il lato sinistro dell'operatore di invocazione (o freccia) del metodo è l'oggetto (o il nome della classe), e il lato destro è il nome del metodo.

  my $pod = File->new( 'perlobj.pod', $dati );
  $pod->salva();

La sintassi -> viene utilizzata anche quando si dereferenzia un riferimento. Sembra essero lo stesso operatore, ma queste sono due operazioni diverse.

Quando si chiama un metodo, la cosa sul lato sinistro della freccia è passata come primo argomento al metodo. Ciò significa che quando chiamiamo Creatura->new(), il metodo new() riceve la stringa "Creatura" come primo argomento. Quando chiamiamo <$fred-parla()>>, la variabile $fred viene passata come primo argomento a parla().

Proprio come con qualsiasi subroutine Perl, tutti gli argomenti passati in @_ sono alias degli argomenti originali. Ciò include l'oggetto stesso. Se si assegna direttamente a $_[0] si cambieranno i contenuti della variabile che contiene il riferimento all'oggetto. Si consiglia di non farlo se non si sa esattamente cosa si sta facendo.

Perl sa a quale package appartiene il metodo guardando il lato sinistro della la freccia. Se la sinistra è un nome di package, Perl cerca il metodo in quel package. Se la sinistra è un oggetto, allora Perl cerca il metodo nel package in cui l'oggetto è stato sottoposto a bless.

Se la sinistra non è né il nome del package, né un oggetto, allora la chiamata al metodo causerà un errore, ma per maggiori sfumature si veda la sezione Variazioni sulle Chiamate ai Metodi.

Ereditarietà >

Abbiamo già parlato dello speciale array @ISA e della direttiva parent.

Quando una classe eredita da un'altra classe, i metodi definiti nella classe genitore sono a disposizione per la classe figlio. Se si tenta di chiamare un metodo su un oggetto che non è definito nella propria classe, Perl cercherà quel metodo anche in tutte le classi genitore che può avere.

  package File::MP3;
  use parent 'File';    # imposta @File::MP3::ISA = ('File');
  my $mp3 = File::MP3->new( 'Andvari.mp3', $dati );
  $mp3->salva();

Dal momento che non abbiamo definito un metodo salva() nella classe File::MP3, Perl guarderà la classe genitrice della classe File::MP3 per trovare il metodo salva(). Se Perl non riesce a trovare un metodo salva() da nessuna parte nella gerarchia di ereditarietà, terminerà.

In questo caso, Perl trova un metodo salva() nella classe File. Si noti che l'oggetto passato a salva() in questo caso è ancora un oggetto File::MP3, anche se il metodo si trova nella classe File.

Si può effettuare un override [ridefinizione, NdT] del metodo di un genitore in una classe figlia. Quando lo facciamo, possiamo ancora chiamare il metodo della classe genitore con la pseudo-classe SUPER.

  sub salva {
      my $self = shift;
      say 'Preparatevi al rock';
      $self->SUPER::salva();
  }

Il modificatore SUPER può solo essere utilizzato per le chiamate ai metodi. Non è possibile utilizzarlo per effettuare chiamate a subroutine regolari o a metodi della classe:

  SUPER::salva($cosa);     # INSUCCESSO: cerca la sub salva() nel package SUPER
  SUPER->salva($cosa);     # INSUCCESSO: cerca il metodo salva() nella classe 
                           #             SUPER
  $cosa->SUPER::salva();   # Okay: cerca il metodo salva() nella classe 
                           #       genitore

Come viene Risolto SUPER

La pseudo-classe SUPER viene risolta nel package in cui viene fatta la chiamata. NON è risolta in base alla classe dell'oggetto. Questo è importante perché permette ai metodi, di differenti livelli all'interno di una profonda gerarchia di ereditarietà, di chiamare correttamente, ognuno di loro, i propri rispettivi metodi nelle classi genitore.

  package A;
  sub new {
      return bless {}, shift;
  }
  sub parla {
      my $self = shift;
      $self->SUPER::parla();
      say 'A';
  }
  package B;
  use parent 'A';
  sub parla {
      my $self = shift;
      $self->SUPER::parla();
      say 'B';
  }
  package C;
  use parent 'B';
  sub parla {
      my $self = shift;
      $self->SUPER::parla();
      say 'C';
  }
  my $c = C->new();
  $c->parla();

In questo esempio, otterremo il seguente output:

  A
  B
  C

Ci sono rari casi in cui questa risoluzione ``basata sul package'' può essere un problema. Se si copia una subroutine da un package ad un altro, la risoluzione di SUPER sarà fatta in base al package originale.

Ereditarietà multipla multipla>

L'ereditarietà multipla denota spesso un problema di progettazione, ma Perl vi dà sempre abbastanza corda per impiccarsi se gliela chiedete.

Per dichiarare classi genitori multiple, è sufficiente passare i nomi delle classi multiple a use parent:

  package PluriFiglio;
  use parent 'Genitore1', 'Genitore2';

Ordine del Metodo di Risoluzione

L'ordine del metodo di risoluzione conta solo nel caso di ereditarietà multipla. Nel caso di ereditarietà singola, Perl cerca semplicemente lungo la catena di ereditarietà per trovare un metodo:

  Progenitore
    |
  Genitore
    |
  Figlio

Se chiamiamo un metodo nell'oggetto di un Figlio e questo metodo non è definito nella classe Figlio, Perl cercherà tale metodo nella classe Genitore e poi, se necessario, nella classe Progenitore.

Se Perl non riesce a trovare il metodo in una di queste classi, terminerà con un messaggio di errore.

Quando una classe ha più genitori, l'ordine di ricerca del metodo diventa più complicato.

Di default, per trovare un metodo Perl effettua una ricerca ``depth-first left-to-right'' [prima in profondità, da sinistra a destra, NdT]. Ciò significa che inizia con il primo genitore nell'array @ISA e poi cerca tutti i suoi genitori, progenitori, ecc. Se non riesce a trovare il metodo, allora passa al successivo genitore nell'originario array @ISA della classe ed effettua la ricerca da lì.

               BisnonnoCondiviso
              /                 \
      NonnoPaterno         NonnoMaterno
              \                 /
              Padre        Madre
                   \      /
                    Figlio

Quindi, dato il diagramma qui sopra, Perl cercherà Figlio, Padre, NonnoPaterno, BisnonnoCondiviso, Madre e infine NonnoMaterno. Questo può essere un problema, perché ora siamo cercando in BisnonnoCondiviso prima di aver controllato tutte le sue classi derivate (cioè prima di aver controllato Madre e NonnoMaterno).

è possibile richiedere un differente ordine di risoluzione con la direttiva mro.

  package Figlio;
  use mro 'c3';
  use parent 'Padre', 'Madre';

Questa direttiva consente di passare all'ordine di risoluzione ``C3''. In parole povere, l'ordine ``C3'' garantisce che le classi genitore condivise non vengano mai ricercate prima delle classi figlio, in modo che ora Perl cercherà: Figlio, Padre, NonnoPaterno, Madre, NonnoMaterno e infine BisnonnoCondiviso. Si noti tuttavia che ciò non sia una ricerca ``breadth-first'' [ricerca in ampiezza, NdT]: tutti gli antenati di Padre (ad eccezione dell'antenato comune) vengono cercati prima che venga preso in considerazione uno qualunque degli antenati di Madre.

L'ordine C3 permette anche di chiamare i metodi in classi di pari livello (sorelle) con la pseudo-classe next. Per ulteriori dettagli su questa funzione, si veda la documentazione di mro.

Metodo di Risoluzione della Cache

Quando Perl cerca un metodo, inserisce la ricerca nella cache, in modo che le future chiamate al metodo non necessitino di una nuova ricerca. Cambiare la classe genitore di una classe oppure aggiungere subroutine ad una classe, invalida la cache per quella classe.

La direttiva mro fornisce alcune funzioni per manipolare direttamente il metodo di risoluzione della cache.

Scrivere Costruttori

Come abbiamo accennato in precedenza, Perl non fornisce alcuna sintassi speciale per scrivere un costruttore. Ciò significa che una classe deve implementare il suo proprio costruttore. Un costruttore è semplicemente un metodo della classe che restituisce un riferimento ad un nuovo oggetto.

Il costruttore può anche accettare parametri aggiuntivi che definiscono l'oggetto. Scriviamo un vero costruttore per la classe File che abbiamo usato in precedenza:

  package File;
  sub new {
      my $classe = shift;
      my ( $percorso, $dati ) = @_;
      my $self = bless {
          percorso => $percorso,
          dati => $dati,
      }, $classe;
      return $self;
  }

Come potete vedere, abbiamo memorizzato il percorso e i dati del file nell'oggetto stesso. Ricordate, dietro le quinte questo oggetto è ancora solo un hash. Più tardi, scriveremo gli accessor per manipolare questi dati.

Per la nostra classe File::MP3, possiamo controllare di essere certi che il percorso che gli stiamo fornendo finisca con ``.mp3'':

  package File::MP3;
  sub new {
      my $classe = shift;
      my ( $percorso, $dati ) = @_;
      die "Non e` possibile creare un file File::MP3 senza l'estensione mp3\n"
          unless $percorso =~ /\.mp3\z/;
      return $classe->SUPER::new(@_);
  }

Questo costruttore consente alla sua classe genitore di effettuare l'effettiva costruzione dell'oggetto.

Attributi

Un attributo è un insieme di dati appartenente ad un particolare oggetto. A differenza di molti linguaggi orientati agli oggetti, Perl non fornisce alcuna speciale sintassi o supporto per la dichiarazione e la manipolazione di attributi.

Gli attributi sono spesso memorizzati nell'oggetto stesso. Ad esempio, se l'oggetto è un hash anonimo, siamo in grado di memorizzare i valori degli attributi dell'hash utilizzando il nome dell'attributo come chiave.

Sebbene sia possibile fare riferimento direttamente a queste chiavi dell'hash al di fuori della classe, è considerata buona norma mascherare tutti gli accessi agli attributi mediante i metodi accessor.

Ciò ha diversi vantaggi. Gli accessor rendono più facile cambiare l'implementazione di un oggetto successivamente, pur conservando le API originali.

Un accessor consente di aggiungere codice aggiuntivo attorno all'accesso dell'attributo. Per esempio, è possibile applicare un valore di default ad un attributo che non è stato impostato nel costruttore, oppure si potrebbe verificare che sia accettabile un nuovo valore per l'attributo.

Infine, utilizzando gli accessor, si rende l'ereditarietà molto più semplice. Le sottoclassi possono utilizzare gli accessor piuttosto che dover conoscere come è implementata internamente una classe genitore.

Scrivere gli Accessor

Come con i costruttori, Perl non fornisce alcuna dichiarazione speciale per la sinstassi degli accessor, quindi le classi devono fornire espressamente metodi accessor già scritti????. Ci sono due tipi comuni di accessor, di sola lettura e di lettura-scrittura.

Un semplice accessor di sola lettura ottiene semplicemente il valore di un singolo attributo:

  sub percorso {
      my $self = shift;
      return $self->{percorso};
  }

Un accessor di lettura-scrittura consentirà al chiamante di impostare il valore così come l'ha ottenuto:

  sub percorso {
      my $self = shift;
      if (@_) {
          $self->{percorso} = shift;
      }
      return $self->{percorso};
  }

Un Codice Intelligente e Sicuro da Mettere da Parte????

Il nostro costruttore e gli accessor non sono molto intelligenti. Non controllano che un $percorso sia definito, né verificano che un $percorso sia un percorso valido per il filesystem.

Fare questi controlli a mano può diventare rapidamente noioso. Anche scrivere un gruppo di accessor a mano è incredibilmente noioso. Ci sono molti moduli su CPAN che vi possono aiutare a scrivere codice più sicuro e più conciso, compresi i moduli raccomandati in perlootut.

Variazioni sulle Chiamate ai Metodi

Perl supporta diversi altri modi per chiamare i metodi oltre all'utilizo di $oggetto->metodo() che abbiamo visto finora.

Nomi di Metodi come Stringhe

Perl consente di utilizzare una variabile scalare contenente una stringa come nome di un metodo:

  my $file = File->new( $percorso, $dati );
  my $metodo = 'salva';
  $file->$metodo();

Questo funziona esattamente come chiamare $file->salva(). Ciò può essere molto utile per la scrittura di codice dinamico. Ad esempio, consente di passare un nome di un metodo da chiamare come parametro per un altro metodo.

Nomi di Classi come Stringhe

Perl consente anche di utilizzare uno scalare contenente una stringa come un nome di una classe:

  my $classe = 'File';
  my $file = $classe->new( $percorso, $dati );

Ancora una volta, questo permette la scrittura di codice molto dinamico.

Riferimenti a Subroutine come Metodi

È inoltre possibile utilizzare un riferimento a subroutine come un metodo:

  my $sub = sub {
      my $self = shift;
      $self->salva();
  };
  $file->$sub();

Questo è esattamente equivalente a scrivere $sub->($file). Può capitare di vedere questo idioma in codice attualmente in produzione combinato con una chiamata a can:

  if ( my $metodo = $oggetto->can('pippo') ) {
      $oggetto->$metodo();
  }

Dereferenziare le Chiamate ai Metodi

Perl consente anche di utilizzare un riferimento ad uno scalare dereferenziato in una chiamata ad un metodo. È impronunciabile, quindi diamo un'occhiata ad un po' di codice:

  $file->${ \'salva' };
  $file->${ restituisce_riferimento_scalare() };
  $file->${ \( restituisce_scalare() ) };
  $file->${ restituisce_riferimento_a_sub() };

Questo funziona se la dereferenziazione produce una stringa o un riferimento a subroutine.

Chiamate a Metodi su Filehandle

Dietro le quinte, i filehandle in Perl sono istanze delle classi IO::Handle o IO::File. Una volta che si ha un filehandle aperto, su di esso è possibile chiamare dei metodi. Inoltre, è possibile chiamare i metodi sui filehandle STDIN, STDOUT e STDERR.

  open my $fh, '>', 'percorso/del/file';
  $fh->autoflush();
  $fh->print('contenuto');
  STDOUT->autoflush();

Invocare i Metodi della Classe

Visto che Perl permette di utilizzare bareword [Letteralmente parola nuda, indica una parola che potrebbe essere la chiamata di una funzione, ma non avendo né & all'inizio né () alla fine è per questo ambigua per perl a tempo di compilazione, NdT] per i nomi dei package e nomi di subroutine, a volte interpreta in maniera erronea il significato di una bareword. Ad esempio, il costrutto Classe->new() può essere interpretato come 'Class'->new() o come Classe()->new(). In inglese, questa seconda interpretazione viene letta come ``chiamare una subroutine chiamata Classe(), quindi chiamare new() come un metodo dandogli il valore restituito da Classe()''. Se vi è una subroutine denominata Classe() nel namespace corrente, Perl interpreterà sempre Classe->new() come la seconda alternativa: una chiamata a new() sull'oggetto restituito da una chiamata a Classe().

È possibile forzare Perl ad usare la prima interpretazione (cioè come un metodo chiamato nella classe denominata ``Classe'') in due modi. In primo luogo, è possibile aggiungere un :: al nome della classe:

    Class::->new()

Perl interpreterà questo sempre come una chiamata ad un metodo.

In alternativa, si può citare il nome della classe:

    'Class'->new()

Naturalmente, se il nome della classe è in uno scalare, Perl farà la cosa giusta:

    my $classe = 'Class';
    $classe->new();

La Sintassi degli Oggetti Indiretti

Al di fuori del caso dei filehandle, l'uso di questa sintassi è sconsigliato, dato che può confondere l'interprete Perl. Per maggiori dettagli si veda più sotto.

Perl supporta un'altra sintassi per l'invocazione dei metodi chiamata notazione dell'``oggetto indiretto''. Questa sintassi si chiama ``indiretta'' perché il metodo è disponibile prima che l'oggetto abbia ricevuto l'invocazione da quel metodo????.

Questa sintassi può essere utilizzata con qualsiasi classe o metodo di un oggetto:

    my $file = new File $percorso, $dati;
    salva $file;

Si consiglia di evitare questa sintassi, per diversi motivi.

In primo luogo, può essere fonte di confusione quando la si legge. Nell'esempio precedente, non è chiaro se salva sia un metodo fornito dalla classe File o semplicemente una subroutine che si aspetta un oggetto file come primo argomento.

Quando viene utilizzato con i metodi della classe, il problema è anche peggiore. Visto che Perl permette ai nomi di subroutine di essere scritti come bareword, Perl deve indovinare se la bareword dopo il metodo è un nome di una classe o il nome di una subroutine. In altre parole, Perl è in grado di risolvere la sintassi sia come File->new( $percorso, $dati ) sia come new( File( $percorso, $dati ) ).

Per fare il parsing di questo codice, Perl utilizza un algoritmo euristico basato su quali nomi di package ha visto, su quali subroutine esistono nel package corrente, su quali bareword sono state viste in precedenza e altri input. Inutile dire che, l'euristica può produrre risultati molto sorprendenti!

La documentazione più vecchia (ed alcuni moduli CPAN) hanno incoraggiato questa sintassi, in particolare per i costruttori, quindi la si può ancora trovare in codice attualmente in produzione. Tuttavia, vi invitiamo a non utilizzarla in nuovo codice.

È possibile forzare Perl a interpretare la bareword come un nome di una classe aggiungendo ad esso ``::'', come abbiamo visto in precedenza:

  my $file = new File:: $percorso, $dati;

bless, blessed e ref

Come abbiamo visto in precedenza, un oggetto è semplicemente una struttura dati che è stata sottoposta a bless in una classe tramite la funzione bless. La funzione bless può richiedere uno o due argomenti:

  my $oggetto = bless {}, $classe;
  my $oggetto = bless {};

Nella prima forma, l'hash anonimo viene sottoposto a bless nella classe $classe. Nella seconda forma, l'hash anonimo viene sottoposto a bless nel pacchetto corrente.

La seconda forma è fortemente sconsigliata, perché interrompe la capacità di una sottoclasse di riutilizzare il costruttore del genitore, ma la si può ancora incontrare in codice esistente.

Se volete sapere se un particolare scalare si riferisce ad un oggetto, è possibile utilizzare la funzione blessed esportata da the Scalar::Util manpage, presente nel core del Perl.

  use Scalar::Util 'blessed';
  if ( defined blessed($cosa) ) { ... }

Se $cosa si riferisce ad un oggetto, allora questa funzione restituisce il nome del package nel quale l'oggetto è stato sottoposto a bless. Se $cosa non contiene un riferimento ad un oggetto sottoposto a bless, la funzione blessed restituisce undef.

Si noti che blessed($cosa) restituisce anche falso se $cosa è stata sottoposta a bless in una classe denominata ``0''. Ciò è possibile, ma piuttosto patologico. Non create una classe denominata ``0'' se non sapete cosa state facendo.

Allo stesso modo, la funzione integrata in Perl ref considera appositamente un riferimento come un oggetto sottoposto a bless????. Se si chiama ref($cosa) e $cosa contiene un riferimento ad un oggetto, verrà restituito il nome della classe in cui l'oggetto è stato sottoposto a bless.

Se si vuole semplicemente verificare che una variabile contenga un riferimento ad un oggetto, si consiglia di utilizzare defined blessed($oggetto), dal momento che ref restituisce valori veri per tutti i riferimenti, non solo quelli di oggetti.

La Classe UNIVERSAL

Tutte le classi ereditano automaticamente dalla classe UNIVERSAL, che è integrata nel core del Perl. Questa classe fornisce un certo numero di metodi, ognuno dei quali può essere chiamato sia su una classe sia su un oggetto. Nella vostra classe è anche possibile scegliere di effettuare un override di alcuni di questi metodi. Se lo si fa, si consiglia di seguire le semantiche integrate, descritte di seguito.

isa($classe)
Il metodo isa [è-un, NdT] restituisce vero se l'oggetto è un membro della classe in $classe, o un membro di una sottoclasse di $classe.

Se si effettua un override di questo metodo, perl non dovrebbe mai sollevare un'eccezione.

DOES($ruolo)
Il metodo DOES [fa, NdT] restituisce vero se il suo oggetto pretende di eseguire il ruolo $ruolo. Di default, questo equivale a isa. Questo metodo è fornito per l'utilizzo di estensioni di sistemi ad oggetti che implementano i ruoli, come Moose e Role::Tiny.

È inoltre possibile effettuare l'override di DOES direttamente all'interno delle classi. Se si effettua l'override di questo metodo, perl non dovrebbe mai sollevare un'eccezione.

can($metodo)
Il metodo can [può, NdT] controlla se la classe o l'oggetto che sono stati chiamati, abbiano un metodo chiamato $metodo. Questo controlla il metodo nella classe e in tutti i suoi genitori. Se il metodo esiste, allora viene restituito un riferimento alla subroutine. Se non esiste allora viene restituito undef.

Se la classe risponde al metodo chiamato tramite AUTOLOAD, si consiglia di effettuare un override di can per restituire un riferimento ad una subroutine per i metodi che il vostro metodo AUTOLOAD gestisce.

Se si effettua l'override di questo metodo, perl non dovrebbe mai sollevare un'eccezione.

VERSION($esigenza)
Il metodo VERSION restituisce il numero di versione della classe (package).

Se viene fornito l'argomento $esigenza allora si controllerà che la versione (come definito dalla variabile $VERSION nel package) corrente sia maggiore o uguale a $esigenza; in caso contrario terminerà. Questo metodo viene chiamato automaticamente dalla forma VERSION di use.

    use Package 1.2 qw(alcune subroutine importate);
    # implica:
    Package->VERSION(1.2);

Si consiglia di utilizzare questo metodo per accedere alla versione di un altro package, piuttosto che guardare direttamente in $Package::VERSION. Il package in cui si sta guardando potrebbe aver effettuato un override del metodo VERSION.

Si consiglia inoltre di utilizzare questo metodo per verificare se un modulo ha una versione sufficiente. L'implementazione interna utilizza il modulo version per fare in modo che i diversi tipi di numeri di versione siano confrontati correttamente.

AUTOLOAD

Se si chiama un metodo che non esiste in una classe, Perl genererà un errore. Tuttavia, se tale classe o una delle sue classi genitore definisce un metodo AUTOLOAD, viene chiamato invece quel metodo AUTOLOAD.

AUTOLOAD viene chiamato come metodo normale, e il chiamante non sa la differenza. Qualunque sia il valore che restituise il metodo AUTOLOAD, viene restituito al chiamante.

Il nome completo del metodo che è stato chiamato è disponibile nel package $AUTOLOAD, globale per la classe. Poiché è globale, se si vuole fare riferimento ad eseguirlo???? senza un prefisso di nome di package sotto strict 'vars', dovete dichiararlo.

  # XXX - questo e` un modo terribile per implementare degli accessor, ma lo si fa
  # per fare un semplice esempio
  our $AUTOLOAD;
  sub AUTOLOAD {
      my $self = shift;
      # Rimuove il qualificatore dal nome originale del metodo...
      my $chiamato = $AUTOLOAD =~ s/.*:://r;
      # C'e` un attributo con questo nome?
      die "Nessun attributo del genere: $chiamato"
          unless exists $self->{$chiamato};
      # Se e` cosi`, lo restituisce...
      return $self->{$chiamato};
  }
  sub DESTROY { } # si veda sotto

Senza la dichiarazione our $AUTOLOAD, questo codice non verrà compilato sotto la direttiva strict.

Come dice il commento, questo non è un buon modo per implementare degli accessor. È lento e di gran lunga troppo ingegnoso. Tuttavia, è possibile vederelo come un modo per fornire gli accessor in vecchio codice Perl. Per delle raccomandazioni su come scrivere codice OO in Perl si veda perlootut.

Se la classe ha un metodo AUTOLOAD, si consiglia vivamente di effettuare l'override di can anche nella vostra classe. Il vostro metodo can di cui si è fatto l'override deve restituire un riferimento a subroutine per qualsiasi metodo a cui risponde AUTOLOAD.

Distruttori

Quando l'ultimo riferimento ad un oggetto non esiste più, l'oggetto viene distrutto. Se si ha un solo riferimento ad un oggetto memorizzato in uno scalare lessicale, l'oggetto viene distrutto quando lo scalare esce dallo scope. Se si memorizza l'oggetto in un package globale, tale oggetto non può uscire dallo scope fino a quando il programma non termina.

Se si vuole fare qualcosa quando l'oggetto viene distrutto, potete definire un metodo DESTROY nella vostra classe. Questo metodo sarà sempre chiamato dal Perl al momento opportuno, a meno che il metodo sia vuoto.

Questo metodo viene chiamato esattamente come qualsiasi altro metodo, con l'oggetto come primo argomento. Non riceve alcun argomento aggiuntivo. Tuttavia, nel distruttore la variabile $_[0] sarà di sola lettura, quindi non vi sarà possibile assegnare un valore.

Se il metodo DESTROY genera un errore, questo errore verrà ignorato. Non sarà inviato a STDERR e non causerà la terminazione del programma. Tuttavia, se il distruttore è in esecuzione all'interno di un blocco eval {}, allora l'errore cambierà il valore di $@.

Poiché i metodi DESTROY possono essere chiamati in qualsiasi momento, si dovrebbe localizzare tutte le variabili globali che potrebbero venire aggiornate nel vostro DESTROY. In particolare, se si utilizza eval {} si dovrebbe localizzare $@ e se si utilizzano system o i backtick [`, NdT], si dovrebbe localizzare $?.

Se nella vostra classe definite un AUTOLOAD, allora Perl chiamerà il vostro AUTOLOAD per gestire il metodo DESTROY. È possibile evitarlo definendo un DESTROY vuoto, come abbiamo fatto nell'esempio dell'autoload. È inoltre possibile controllare il valore di $AUTOLOAD ed effettuare un return senza fare niente quando lo si chiama per gestire DESTROY????.

Distruzione Globale

L'ordine con il quale vengono distrutti gli oggetti durante la distruzione globale prima di uscire dal programma è imprevedibile. Ciò significa che qualsiasi oggetto contenuto da un vostro oggetto potrebbe essere già stato distrutto. Prima di chiamare un metodo su un oggetto ``contenuto'' si dovrebbe verificare che tale oggetto sia definito:

  sub DESTROY {
      my $self = shift;
      $self->{handle}->close() if $self->{handle};
  }

È possibile utilizzare la variabile ${^GLOBAL_PHASE} per rilevare se si è attualmente in fase di distruzione globale:

  sub DESTROY {
      my $self = shift;
      return if ${^GLOBAL_PHASE} eq 'DESTRUCT';
      $self->{handle}->close();
  }

Si noti che questa variabile è stata aggiunta nel Perl 5.14.0. Se si desidera rilevare la fase di distruzione globale sulle vecchie versioni di Perl, è possibile utilizzare il modulo Devel::GlobalDestruction su CPAN.

Se il vostro metodo DESTROY emette un avvertimento durante la distruzione globale, l'interprete Perl aggiungerà la stringa ``during global destruction'' [``durante la distruzione globale'', NdT] nell'avvertimento.

Durante la distruzione globale, Perl effettuerà sempre il garbage collection degli oggetti prima dei riferimenti non più sottoposti a bless. Per ulteriori informazioni sulla distruzione globale si veda perlhacktips/PERL_DESTRUCT_LEVEL.

Oggetti Non-Hash

Tutti gli esempi fino ad ora hanno mostrato oggetti sulla base di un hash sottoposto a bless. Tuttavia, è possibile sottoporre a bless qualsiasi tipo di struttura di dati o referent, compresi scalari, globs e subroutine. Questi tipi di cose si possono vedere nel codice attualmente in produzione.

Ecco un esempio di modulo come uno scalare sottoposto a bless:

  package Tempo;
  use strict;
  use warnings;
  sub new {
      my $classe = shift;
      my $tempo = time;
      return bless \$tempo, $classe;
  }
  sub epoch { 
      my $self = shift;
      return ${ $self };
  }
  my $tempo = Tempo->new();
  print $tempo->epoch(); # data di riferimento; nella cultura Unix il 1/1/1970 00:00:00, NdT

Oggetti Inside-Out

In passato, la comunità Perl sperimentò una tecnica chiamata ``oggetti inside-out''. Un oggetto inside-out memorizza i suoi dati al di fuori del riferimento all'oggetto, indicizzati con una proprietà unica dell'oggetto, ad esempio l'indirizzo di memoria, piuttosto che nell'oggetto stesso. Ciò ha il vantaggio di far rispettare l'incapsulamento degli attributi dell'oggetto, poiché i dati non vengono memorizzati nell'oggetto stesso.

Questa tecnica è stata popolare per un po' (ed è stata raccomandata nel libro di Damian Conway Perl Best Practices), ma non ha mai raggiunto una accettazione universale. Il modulo the Object::InsideOut manpage su CPAN fornisce l'implementazione completa di questa tecnica, e potreste vedere altri moduli inside-out in codice attualmente in produzione.

Ecco un semplice esempio della tecnica, utilizzando il modulo the Hash::Util::FieldHash manpage presente nel core. Questo modulo è stato aggiunto al core per supportare le implementazioni degli oggetti inside-out.

  package Tempo;
  use strict;
  use warnings;
  use Hash::Util::FieldHash 'fieldhash';
  fieldhash my %tempo_per;
  sub new {
      my $classe = shift;
      my $self = bless \( my $oggetto ), $classe;
      $tempo_per{$self} = time;
      return $self;
  }
  sub epoch {
      my $self = shift;
      return $tempo_per{$self};
  }
  my $tempo = Tempo->new;
  print $tempo->epoch;

Pseudo-hash

Gli pseudo-hash erano una funzionalità sperimentale introdotta in versioni precedenti di Perl e rimossa a partire dalla 5.10.0. Un pseudo-hash è un riferimento ad un array a cui si può accedere utilizzando le chiavi con nome, come in un hash. Potreste imbattervi in codice attualmente in produzione che lo utilizza. Per ulteriori informazioni si veda la direttiva fields.


SI VEDA ANCHE

perlootut è un tutorial cortese e garbato sulla programmazione orientata agli oggetti in Perl. Si dovrebbe anche controllare perlmodlib per alcune guide di stile sulla costruzione sia dei moduli che delle classi.


TRADUZIONE

Versione

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

   perl -MPOD2::IT -e print_pod perlobj_v5.16.0

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

Traduttore

Traduzione a cura di dree.

Revisore

Revisione a cura di .


Mon Jun 11 22:02:20 2012