Indice

Capitolo 8.
La programmazione orientata agli oggetti






   1. Cenni di programmazione orientata agli oggetti

Object Oriented Programming è una sigla che definisce un modo radicalmente diversa di concepire un programma. Nella programmazione strutturata canonica (ossia quella che abbiamo affrontato sino ad oggi), i dati e le procedure che li manipolano sono due oggetti distinti. Le strutture dati sono da un lato e spesso sono globali all'intero programma. Il codice che manipola questi dati è l'altra parte distinta, che può essere applicata a differenti strutture dati, purché caratterizzate dalla medesima "ossatura" (pena imprevedibili conseguenze sui dati stessi).

     Nella Programmazione Orientata agli Oggetti invece la Classe è l'origine di tutta l'impostazione di un programma. La classe è il concetto che astrae la descrizione di una parte della struttura dati, combinata con la parte del programma che ne manipola, interroga ed imposta i contenuti.

     Il reale vantaggio di questa concezione del programma è che il programmatore che usa un oggetto scritto da altri non tocca mai i dati con mano. Tutta la manipolazione dei dati è affidata alle subroutine che sono associate a quella particolare struttura dati.

    


     Come potete notare l'oggetto contiene tanto la parte dati quanto la parte codice. Le subroutine che trattano con il dato, essendo strettamente correlati con QUELLA struttura dati, vengono chiamate metodi (in quanto costituiscono i metodi per modificare la struttura dati).

     Per ogni classe esiste sempre almeno un metodo chiamato costruttore che serve a istanziare la classe, serve cioé a "costruire" un oggetto (ossia una copia della classe con però propria identità). Ad esempio, se abbiamo una classe Lampadina che descrive come sono fatte le lampadine (attenzione! TUTTE le lampadine di questo mondo, ossia quali parti hanno in comune), dalla classe (che di fatto è solo un "modello" di quell'oggetto) occorre ricavare un singolo oggetto al quale poter dare una specifica identità, ossia un set di valori che lo distinguano da tutti gli altri oggetti istanziati dalla sua classe.

     Potremo quindi, attraverso il costruttore, ricavare una lampadina da 50W e una da 120W partendo dalla stessa classe. Per tradizione il costruttore in molti linguaggi si chiama new oppure si chiama come la classe stessa (nel nostro caso Lampadina). Perl non pone questo tipo di restrizioni e consente al programmatore di scegliere il nome che preferisce. Tuttavia in molti casi il programmatore chiama il costruttore new per una serie di motivi che capiremo in seguito.

     Altra nota di distinzione di Perl da altri linguaggi di programmazione è che una classe può contenere più di un costruttore, che eventualmente agisca in maniera particolare a seconda dei dati forniti.


   
2. La Programmazione Orientata agli Oggetti in Perl

Non è molto quello che cé' da imparare per programmare Perl Object Oriented! Questo perché Perl usa concetti e strutture già note, ma con un paio di accorgimenti in più!

     Primo: Un oggetto è solo una "cosa referenziata" che sa da quale classe proviene.

Secondo: Una classe è solo un package che contiene i metodi per trattare con un particolare oggetto.

Terzo: Un metodo è solo una subroutine che riceve come primo parametro un reference alla classe della quale fa parte

Lasciamo da parte la prima affermazione che si capisce meglio in seguito.... Come è costruito un oggetto in Perl? Vediamone uno che rappresenti una lampadina!


Un primo oggetto "Lampadina" Fig. 1
 
    1:  #!/usr/bin/perl
    2: 
    3:  package Lampadina;
    4: 
    5:  sub new {
    6:    my $self = {};
    7:    bless $self;
    8:    return $self;
    9:  }
 

Alla linea 3 indichiamo che il package corrente è Lampadina. Questa operazione, in un contesto Object Oriented include anche l'implicita assunzione che il package che stiamo creando è in reatà anche una classe, ossia l'astrazione di un oggetto.

     Ora pensate al punto uno: Un oggetto è solo una "cosa referenziata" che sa da quale classe proviene. Guardate la linea 6. La variabile $self È il nostro oggetto! self è la "cosa referenziata" che sa da quale classe proviene. Prendiamocela comoda. Un oggetto, o almeno il suo midollo, è in realtà solo una variabile che subisce successivamente un trattamento speciale nel costruttore. Il motivo della scelta di una hash risulterà evidente in seguito. Quello che pero ora dovete pensare è che $self sia il nostro oggetto.

     Bene! Studiamoci ora la riga 7, quella che "fà la magia"! bless è una parola chiave di Perl che "oggettifica" una variabile. Questo è il trattamento speciale di cui parlavamo prima. Con bless la variabile viene legata alla classe nella quale è definita. Infatti il costruttore new come ultima cosa ritorna la variabile.


   
3. Creiamo il primo oggetto

In realta' il nostro costruttore è ancora un po' scarno. Ma, anche se per ora fa poco senso, creiamo la nostra prima lampadina:


La prima "Lampadina" Fig. 2
 
    1:  #!/usr/bin/perl
    2: 
    3:  use Lampadina;
    4: 
    5:  my $lampadina = new Lampadina;
 

La direttiva use dice al compilatore di caricare la libreria (Modulo) Lampadina. Con la riga 5 assegnamo alla variabile $lampadina il reference ritornato dal costruttore new della nostra classe Lampadina.

     Ora $lampadina contiene la nostra "istanza" di Lampadina. Attraverso il reference alla classe potremo in seguito chiamare i metodi della nostra lampadina.


   
4. Descriviamo una lampadina

Quali sono le caratteristiche di una lampadina? La potenza, il colore della luce, il tipo di attacco (Edison o a baionetta). Decidiamo di dare alla nostra classe la capacità di poter descrivere la lampadina secondo questi parametri. Per far questo dobbiamo decidere dove sistemare questi dati e con quali metodi modificarli.

     Partiamo con i dati, in modo da capire perché di solito si usa una hash come cuore di un oggetto. Per ciascuno dei nostri dati definiamo una chiave nella hash. E per coseguenza partiamo dalla definizione dei metodi che su essi interagiscono. Una caratteristica fondamentale di una lampadina è se essa sia accesa o spenta. Per questo definiamo il metodo switch ed il metodo state per cambiare lo stato dell'accensione e interrogare la lampadina sul suo stato.


Miglioriamo la lampadina Fig. 3
 
    1:  #!/usr/bin/perl
    2: 
    3:  package Lampadina;
    4: 
    5:  sub new {
    6:    my $self = shift() || {
    7:      stato => 'spenta',
    8:      potenza => '0W',
    9:      attacco => 'baionetta',
   10:      luce => 'gialla',
   11:    };
   12:    bless $self;
   13:    return $self;
   14:  }
   15: 
   16:  sub switch {
   17:    my $self = shift;
   18:    $self{state} = $self{state} eq 'accesa' ? 'spenta' : 'accesa';
   19:  }
   20: 
   21:  sub state {
   22:    my $self = shift;
   23:    return $self{state};
   24:  }
   25: 
   26:  package main;
   27: 
   28:  my $lampadina = new Lampadina({
   29:    stato => 'spenta',
   30:    potenza => '60W',
   31:    attacco => 'edison',
   32:    luce => 'bianca',
   33:  });
   34: 
   35:  if ( $lampadina->state() eq 'spenta' ) {
   36:    $lampadina->switch();
   37:    print "la lampadina ora è accesa\n";
   38:  } else {
   39:    $lampadina->switch();
   40:    print "la lampadina è spenta\n";
   41:  }
 

Quali le differenze? Molte direi!

     Prima di tutto una piccola grande modifica al costruttore: il cuore del nostro oggetto, la hash $self viene ora scelta prima (se possibile) una reference di hash che viene passata come primo argomento del metodo new. In caso questa non sia disponibile viene assegnata una nuova hash contenente valori di default. Tuttavia non è il nostro caso dato che l'oggetto Lampadina da noi creato contiene una hash predefinita di valori che ne cambia ben 3 su 4 rispetto al default.

     Poi abbiamo la prima definizione di metodi oltre al costruttore! Il primo, switch cambia alla linea 18 lo stato della lampadina, il secondo, state ritorna lo stato della lampadina. Entrambi hanno come prima linea di codice my $self = shift;. Ricordate il terzo punto? Un metodo è solo una subroutine che riceve come primo parametro un reference all'oggetto del quale fa parte! Semplice! Quindi all'interno del metodo dobbiamo recuperare questo reference in una variabile, dato che così abbiamo la possibilità di interagire con la struttura dati del nostro oggetto!

     Per esempio il metodo switch cambia lo stato della lampadina (accesa/spenta) eseguendo una verifica su $self{stato}: se è "accesa" allora cambia in "spenta", in caso contrario cambia in "accesa". Notate come il nostro programma in seguito esegua una verifica su $lampadina->state() e non sulla chiave della hash (alla quale per altro non ha nemmeno accesso). Questo perche' il dato deve essere protetto dall'utenza che usa l'oggetto, presumendo che l'utenza possa anche non conoscere i possibili valori di $self{stato} e supponendo che possa quindi per errore impostare quella chiave a "senza_corrente" oppure a "rotta".


   
5. I metodi e l'operatore ->

Come risulta dall'esempio precedente, l'operatore -> (detto operatore di dereferenziazione) serve a chiamare un metodo di un oggetto. Ad esempio abbiamo usato la sintassi $lampadina->state() per chiamare il metodo state dell'oggetto $lampadina. La sintassi è in tutta analoga a state $lampadina, ma l'operartore -> effettua la dereferenziazione e passa al metodo come primo parametro il reference all'oggetto.


   
6. Ereditarietà

In Programmazione Orientata agli Oggetti è comune riferirsi ad una caratteristica di un oggetto: la sua ereditarietà. Con questo concetto si intende esprimere chi siano i "genitori" dell'oggetto, ossia da chi l'oggetto discenda. Dai propri genitori un oggetto eredita tutti i metodi che in esso non vengono direttamente ridefiniti.

     In Perl l'ereditarietà viene implementata con un meccanismo estremamente semplice: l'array @ISA, lo stesso già usato dai Moduli. Quando un oggetto non definisce un metodo che da esso viene chiamato (es. $lampadina->svita()) Perl attiva un meccanismo di ricerca tale per cui estrae a turno dall'array @ISA tutti gli oggetti che sono genitori di quello corrente; per ciascuno di questi oggetti estratti cerca il metodo in esso e in tutti gli oggetti definiti dall'array @ISA di questo e così via, ricorsivamente. Osservate l'oggetto Faro definito di seguito.


Ereditarietà Fig. 4
 
    1:  #!/usr/bin/perl
    2: 
    3:  package Faro;
    4: 
    5:  use Lampadina;
    6:  @ISA = ( Lampadina );
    7: 
    8:  sub new {
    9:    my $self = {};
   10:    bless $self;
   11:    return $self;
   12:  }
   13: 
   14:  sub punta {
   15:    my $self = shift();
   16:    my $direction = shift();
   17:    $self{direction} = $direction;
   18:  }
 

La classe Faro estende la classe Lampadina. Se ora creiamo un oggetto Faro, potremo:


Applicazione pratica di ereditarietà Fig. 5
 
    1:  #!/usr/bin/perl
    2: 
    3:  use Faro;
    4: 
    5:  my $faro = new Faro;
    6:  $faro->switch();
    7:  $faro->punta('Nord');
 

Notate come la classe Faro non contenga il metodo switch che viene invece definito dalla classe Lampadina. Il metodo viente comunque recuperato attraverso la gerarchia degli array @ISA (che in questo caso è composta da solo l'oggetto Lampadina). Né nella nostra applicazione si menziona esplicitamente la classe Lampadina. È richiesto il solo caricamento del Modulo che contiene Faro (use Faro;).



Inizio Capitolo Indice