20.01.2015 Views

la programmazione orientata agli oggetti - itis magistri cumacini

la programmazione orientata agli oggetti - itis magistri cumacini

la programmazione orientata agli oggetti - itis magistri cumacini

SHOW MORE
SHOW LESS

You also want an ePaper? Increase the reach of your titles

YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.

1<br />

LA PROGRAMMAZIONE ORIENTATA AGLI OGGETTI<br />

(© I.D. Falconi ~ 24.01.09)<br />

INTRODUZIONE<br />

La <strong>programmazione</strong> <strong>orientata</strong> <strong>agli</strong> <strong>oggetti</strong> (OOP) rappresenta una modalità di <strong>programmazione</strong><br />

strutturata attraverso <strong>la</strong> quale il problema da risolvere viene affrontato rappresentando <strong>la</strong> realtà come<br />

un insieme di elementi (<strong>oggetti</strong>) interagenti fra loro, ciascuno in grado di compiere una serie di azioni<br />

specifiche.<br />

Ad esempio, supponiamo di voler progettare un applicazione che realizzi una <strong>la</strong>vagna elettronica<br />

(schermo) sul<strong>la</strong> quale si possano disegnare, cancel<strong>la</strong>re, spostare, ingrandire e rimpicciolire una serie di<br />

figure (punti, cerchi, triangoli, ...).<br />

In tale applicazione sono individuabili<br />

- un oggetto <strong>la</strong>vagna, caratterizzato da una serie di proprietà che ne descrivono le caratteristiche<br />

(colore, <strong>la</strong>rghezza, altezza,...) e dalle azioni che <strong>la</strong> <strong>la</strong>vagna è in grado di eseguire su richiesta dell’utente<br />

(cancel<strong>la</strong>zione, visualizzazione,...).<br />

Inoltre, sul<strong>la</strong> <strong>la</strong>vagna potranno essere rappresentate figure di vario tipo:<br />

- <strong>oggetti</strong> di tipo punto, caratterizzati da posizione e colore e in grado di essere visualizzati o nascosti,<br />

spostati,...<br />

- <strong>oggetti</strong> di tipo cerchio, caratterizzati da posizione, colore, raggio e in grado di compiere le stesse<br />

azioni degli <strong>oggetti</strong> di tipo punto, oltre a poter essere ingranditi o rimpiccioliti.<br />

- <strong>oggetti</strong> di tipo rettangolo ...<br />

Da quanto detto sopra, possiamo dire che, nelle linee essenziali, per definire un oggetto se ne devono<br />

specificare le caratteristiche e le azioni che è in grado di compiere.<br />

In termini di codice, ciò equivale a definire un record i cui campi descrivono le caratteristiche<br />

(proprietà) dell'oggetto e una serie di procedure o funzioni (metodi) che realizzano le azioni<br />

dell'oggetto.<br />

type<br />

TCoordinata = integer;<br />

TPunto = OBJECT<br />

{proprietà}<br />

x,y: TCoordinata;<br />

colore: byte;<br />

{metodi}<br />

procedure crea (x0,y0:TCoordinata;col0:byte);<br />

procedure cambiaColore(nuovoColore:byte);<br />

...<br />

END;<br />

Si noti che:<br />

• La dichiarazione delle proprietà deve precedere quel<strong>la</strong> dei metodi.<br />

• I nomi delle proprietà e quelli dei parametri formali dei metodi devono essere diversi.


2<br />

Il processo utilizzato per definire i metodi ricorda le unit PASCAL:<br />

all'interno dell'oggetto si specifica solo l'intestazione dei metodi, come nel<strong>la</strong> sezione<br />

interface di una unit, mentre <strong>la</strong> definizione del codice dei metodi avviene all'esterno del<strong>la</strong><br />

definizione dell'oggetto, nel<strong>la</strong> sezione del<strong>la</strong> dichiarazione delle procedure, similmente a<br />

quanto accade nel<strong>la</strong> sezione implementation delle unit.<br />

E' pratica comune creare delle librerie (unit) di <strong>oggetti</strong>, dichiarando i tipi oggetto nel<strong>la</strong> sezione di<br />

interfaccia e il codice dei metodi nel<strong>la</strong> sezione di implementazione.<br />

unit Punti;<br />

interface (***************************************)<br />

type<br />

TCoordinata = integer;<br />

TPunto = OBJECT<br />

{proprietà}<br />

x,y: TCoordinata;<br />

colore: byte;<br />

{metodi}<br />

procedure crea (x0,y0:TCoordinata;col0:byte);<br />

procedure cambiaColore(nuovoColore:byte);<br />

procedure disegna;<br />

END;<br />

implementation (*********************************)<br />

uses GRAPH;<br />

{----------- implementazione metodi dell'oggetto TPunto -------------}<br />

procedure TPunto.crea (x0,y0:TCoordinata;col0:byte);<br />

begin<br />

x:=x0;<br />

y:=y0;<br />

colore:=col0;<br />

end;<br />

procedure TPunto.cambiaColore(nuovoColore:byte);<br />

begin<br />

colore:=nuovoColore;<br />

end;<br />

procedure TPunto.disegna;<br />

begin<br />

putPixel(x,y,colore);<br />

end;<br />

{------------------------------------------------------------------------------}<br />

BEGIN (**************************************)<br />

grD:=Detect;<br />

initGraph(grD,grM,' ');<br />

clearDevice<br />

END.


3<br />

program usaPunti;<br />

uses Crt,Punti;<br />

var<br />

puntoRossoBlu:TPunto;<br />

BEGIN<br />

puntoRossoBlu.crea(100,50,red);<br />

puntoRossoBlu.disegna;<br />

de<strong>la</strong>y(2000);<br />

with puntoRossoBlu do<br />

begin<br />

cambiacolore(blue);<br />

disegna;<br />

end;<br />

repeat until keypressed;<br />

END.<br />

ISTANZE DI TIPI OBJECT<br />

Spesso si fa riferimento ai TIPI OBJECT definendoli CLASSI e riservando il termine OGGETTO alle<br />

ISTANZE (ovvero alle variabili) di tali c<strong>la</strong>ssi.<br />

puntoRossoBlu è un oggetto, TPunto è <strong>la</strong> c<strong>la</strong>sse di cui esso è un'istanza.<br />

Le istanze dei tipi object possono essere dichiarate, come qualsiasi altra variabile, sia in memoria<br />

statica che dinamica:<br />

type<br />

TPuntoPtr = ^TPunto;<br />

var<br />

punto : TPunto; {statica: già utilizzabile}<br />

puntoPtr : TPuntoPtr; {dinamica: da allocare con new prima di poter essere utilizzata}<br />

ACCESSO ALLE PROPRIETA' E AI METODI<br />

Dall'esempio sopra riportato emergono alcune osservazioni:<br />

• Per accedere alle parti di un oggetto si utilizza, come nei record, <strong>la</strong> notazione puntata o il<br />

costrutto with.<br />

• Dall'interno del codice di un metodo è possibile fare riferimento direttamente alle proprietà<br />

del re<strong>la</strong>tivo oggetto.<br />

• Poiché i metodi di un tipo di oggetto vengono implementati come procedure esterne <strong>agli</strong> <strong>oggetti</strong>,<br />

è necessario che nell'intestazione dei metodi questi siano preceduti dal nome del tipo di<br />

oggetto a cui appartengono.


4<br />

procedure TPunto.cambiaColore(nuovoColore:byte);<br />

begin<br />

colore := nuovoColore;<br />

end;<br />

osserviamo ora il seguente codice:<br />

punto1.cambiaColore(red);<br />

punto2.cambiaColore(blue);<br />

Sembra presentarsi un problema:<br />

i due <strong>oggetti</strong> punto1 e punto2, essendo istanze del<strong>la</strong> stessa c<strong>la</strong>sse TPunto, condividono lo stesso codice<br />

per il metodo cambiaColore. Il compi<strong>la</strong>tore traduce quindi le due chiamate di cambiaColore con un salto<br />

allo stesso indirizzo. A tale indirizzo si trova l'assegnazione di un nuovo colore al<strong>la</strong> proprietà colore, ma<br />

di quale istanza<br />

In realtà, ogni volta che viene richiamato un metodo, viene inizializzata una variabile di sistema,<br />

chiamata self, contenente l'indirizzo dell'istanza dell'oggetto che ha effettuato <strong>la</strong> chiamata.<br />

E' come se all'interno di ogni metodo vi fosse il seguente codice nascosto:<br />

with self do<br />

begin<br />

{codice del metodo}<br />

...<br />

end<br />

PROPRIETA' FONDAMENTALI<br />

La OOP si basa su tre proprietà fondamentali:<br />

• INCAPSULAMENTO<br />

• EREDITARIETA<br />

• POLIMORFISMO<br />

INCAPSULAMENTO : tutto ciò che riguarda l'oggetto deve essere definito al suo interno. In teoria,<br />

l'utilizzatore dell'oggetto si deve servire unicamente dei metodi, evitando l'accesso diretto alle<br />

proprietà.<br />

Per modificare il colore del punto si dovrà utilizzare il metodo cambiaColore piuttosto che modificare<br />

direttamente le proprietà colore. In teoria, ogni proprietà disponibile all'utilizzatore dovrebbe avere<br />

un corrispondente metodo che ne possa impostare o leggere il valore.


5<br />

TPunto = OBJECT<br />

{proprietà}<br />

x,y: TCoordinata;<br />

colore: byte;<br />

{metodi}<br />

procedure crea (x0,y0:TCoordinata;col0:byte);<br />

function leggiX: TCoordinata;<br />

procedure cambiaX(nuovaX:TCoordinata);<br />

function leggiY: TCoordinata;<br />

procedure cambiaY(nuovaX:TCoordinata);<br />

function leggiColore: byte;<br />

procedure cambiaColore(nuovoColore:byte);<br />

...<br />

END;<br />

Rispettare il principio dell'incapsu<strong>la</strong>mento rende il codice più sicuro e facilmente modificabile: il fatto<br />

di mantenere nascosti i dett<strong>agli</strong> implementativi all'utilizzatore degli <strong>oggetti</strong> rende possibili future<br />

modifiche del codice dei metodi senza dover necessariamente riscrivere i programmi già ri<strong>la</strong>sciati che<br />

utilizzano tali metodi (ovviamente, pur di mantenerne invariata l'intestazione).<br />

Ad esempio, nei metodi cambiaX e cambiaY si potrebbe aggiungere un controllo che limiti i valori delle<br />

coordinate in un certo intervallo.<br />

EREDITARIETA': è possibile definire una gerarchia di <strong>oggetti</strong> che condividono proprietà e metodi. I<br />

discendenti ereditano tutto ciò che appartiene <strong>agli</strong> antenati, con <strong>la</strong> possibilità di aggiungere ulteriori<br />

proprietà e metodi che ne caratterizzano il comportamento.<br />

Ad esempio, tutte le figure hanno delle caratteristiche in comune con il punto: una posizione, un colore,<br />

<strong>la</strong> capacità di cambiare colore ...<br />

E' conveniente allora definire l'oggetto punto come antenato e far discendere da esso tutti i diversi<br />

tipi di figure.<br />

Si noti che l'antenato potrebbe essere un oggetto che non sarà mai utilizzato in un'applicazione, ma <strong>la</strong><br />

cui funzione è semplicemente quel<strong>la</strong> di "trasmettere" delle proprietà e dei metodi ai suoi discendenti.<br />

La sintassi per indicare il rapporto di discendenza è <strong>la</strong> seguente:<br />

type<br />

TFiglio = OBJECT (TPadre)<br />

….<br />

END;


6<br />

Aggiungiamo qualche metodo al<strong>la</strong> dichiarazione di TPunto (premettendo che successivamente sarà<br />

necessario modificar<strong>la</strong>) e facciamo discendere da esso un nuovo tipo TCerchio:<br />

type<br />

TPunto = OBJECT<br />

{proprietà}<br />

x,y: TCoordinata;<br />

colore: byte;<br />

{metodi}<br />

procedure crea (x0,y0:TCoordinata;col0:byte);<br />

function leggiX: TCoordinata;<br />

procedure cambiaX(nuovaX:TCoordinata);<br />

function leggiY: TCoordinata;<br />

procedure cambiaY(nuovaX:TCoordinata);<br />

function leggiColore: byte;<br />

procedure cambiaColore(nuovoColore:byte);<br />

procedure disegna;<br />

procedure cancel<strong>la</strong>;<br />

procedure sposta(nuovaX,nuovaY:TCoordinata);<br />

END;<br />

TCerchio = OBJECT (TPunto)<br />

{ proprietà e metodi ereditati da non dichiarare<br />

x,y: TCoordinata;<br />

colore: byte;<br />

procedure crea (x0,y0:TCoordinata;col0:byte);<br />

function leggiX: TCoordinata;<br />

procedure cambiaX(nuovaX:TCoordinata);<br />

function leggiY: TCoordinata;<br />

procedure cambiaY(nuovaX:TCoordinata);<br />

function leggiColore: byte;<br />

procedure cambiaColore(nuovoColore:byte);<br />

procedure disegna;<br />

procedure cancel<strong>la</strong>;<br />

procedure sposta(nuovaX,nuovaY:TCoordinata);<br />

}<br />

{ nuove proprietà e metodi caratteristici del cerchio }<br />

raggio:integer;<br />

function leggiRaggio: integer;<br />

procedure cambiaRaggio(nuovoRaggio:integer);<br />

END;<br />

Si noti, nel<strong>la</strong> dichiarazione di TCerchio, l'indicazione fra parentesi dell'antenato.


7<br />

POLIMORFISMO: tradotta al<strong>la</strong> lettera, tale proprietà fa riferimento al<strong>la</strong> capacità di un oggetto di<br />

assumere "più forme". Più esattamente, in una gerarchia di <strong>oggetti</strong>, ciascun oggetto realizza le proprie<br />

azioni secondo una propria modalità specifica.<br />

Il punto e il cerchio sono visivamente rappresentati in modo diverso. Il metodo disegna, comune a<br />

entrambi perché ereditato, dovrà disegnare figure differenti a seconda dell'oggetto che lo invoca. In<br />

un certo senso è come se il metodo si "adattasse" all'oggetto.<br />

E' anche vero, però, che il metodo crea, non sarà in grado di assegnare un valore al raggio, poiché<br />

questa proprietà non è posseduta dal punto.<br />

Per capire come possa realizzarsi quanto richiesto dal POLIMORFISMO è indispensabile entrare<br />

maggiormente nei dett<strong>agli</strong>.<br />

RIDEFINIZIONE DEI METODI EREDITATI<br />

Un oggetto discendente eredita proprietà e metodi dell'antenato. Capita spesso, però, che il<br />

comportamento del discendente sia diverso da quello dell'antenato. Si rende quindi necessario<br />

"personalizzare" ovvero ridefinire alcuni metodi.<br />

Per ridefinire un metodo ereditato bisogna specificare nel discendente un nuovo metodo con lo stesso<br />

nome e, se necessario, con un diverso insieme di parametri. Anche il codice, ovviamente, andrà riscritto.<br />

In generale, tutti gli <strong>oggetti</strong> necessitano di un metodo di inizializzazione per "riempire" le proprietà<br />

dell'oggetto (ciò che rende un'istanza diversa da un'altra). Un discendente, quindi, avendo ulteriori<br />

proprietà oltre a quelle ereditate, necessiterà sicuramente di un'inizializzazione diversa rispetto ai<br />

propri antenati.<br />

procedure TPunto.crea (x0,y0:TCoordinata;col0:byte);<br />

begin<br />

x := x0;<br />

y := y0;<br />

colore := col0;<br />

end;<br />

procedure TCerchio.crea(x0,y0:TCoordinata;col0:byte;r0:integer);<br />

begin<br />

TPunto.crea(x0,y0,col0); {ereditata}<br />

raggio := r0;<br />

end;<br />

E' una buona norma inizializzare un'istanza servendosi del metodo di inizializzazione ereditato<br />

dall'antenato. Così facendo, eventuali modifiche apportate successivamente <strong>agli</strong> antenati verranno<br />

automaticamente ereditate da tutti i discendenti.<br />

Si noti <strong>la</strong> sintassi per richiamare il metodo ereditato: il nome del metodo viene fatto precedere dal<br />

nome del<strong>la</strong> c<strong>la</strong>sse.


8<br />

Ma a che serve ereditare un metodo se poi è necessario ridefinirlo Per dare una risposta a questa<br />

domanda, bisogna prima chiarire i meccanismi di compi<strong>la</strong>zione che rendono possibile <strong>la</strong> realizzazione del<br />

polimorfismo. Lo facciamo attraverso un esempio:<br />

consideriamo l'implementazione dei metodi disegna,cancel<strong>la</strong>,sposta per le due c<strong>la</strong>ssi viste:<br />

(************** metodi del<strong>la</strong> c<strong>la</strong>sse TPunto ***********************************)<br />

...<br />

procedure TPunto.disegna;<br />

begin<br />

putPixel(x,y,colore);<br />

end;<br />

procedure TPunto.cancel<strong>la</strong>;<br />

begin<br />

putPixel(x,y,b<strong>la</strong>ck); {supponendo che lo sfondo sia nero}<br />

end;<br />

procedure TPunto.sposta(nuovaX,nuovaY:TCoordinata);<br />

begin<br />

cancel<strong>la</strong>;<br />

x:=nuovaX;<br />

y:=nuovaY;<br />

disegna;<br />

end;<br />

(************** metodi del<strong>la</strong> c<strong>la</strong>sse TCerchio ***********************************)<br />

...<br />

procedure TCerchio.disegna;<br />

begin<br />

setColor(colore);<br />

circle(x,y,raggio);<br />

end;<br />

procedure TCerchio.cancel<strong>la</strong>;<br />

begin<br />

setColor(b<strong>la</strong>ck); {supponendo che lo sfondo sia nero}<br />

circle(x,y,raggio);<br />

end;<br />

procedure TCerchio.sposta(nuovaX,nuovaY:TCoordinata);<br />

begin<br />

cancel<strong>la</strong>;<br />

x:=nuovaX;<br />

y:=nuovaY;<br />

disegna;<br />

end;<br />

Possiamo notare che il codice del metodo sposta è identico per le due c<strong>la</strong>ssi. Si potrebbe pensare di non<br />

ridichiarare il metodo sposta e di ereditarlo da TPunto.<br />

Vediamo cosa succederebbe in tal caso.<br />

Supponiamo di avere un oggetto cerchio di tipo TCerchio e di richiamarne il metodo sposta:<br />

cerchio.sposta(50,70);


9<br />

Quando il compi<strong>la</strong>tore trova una chiamata ad un metodo, per prima cosa cerca tale metodo fra quelli del<br />

tipo di oggetto chiamante. Se non lo trova, risale ordinatamente lungo <strong>la</strong> gerarchia. Una volta trovato,<br />

traduce <strong>la</strong> chiamata con un salto all'indirizzo assegnato al metodo durante <strong>la</strong> sua compi<strong>la</strong>zione.<br />

Nell'ipotesi di avere ereditato il metodo sposta da TPunto, il compi<strong>la</strong>tore, non avendolo trovato tra<br />

quelli di TCerchio, avrà tradotto tale chiamata con un salto all'indirizzo di compi<strong>la</strong>zione del metodo<br />

TPunto.sposta.<br />

Una volta all'interno di tale codice, quindi, tutte le successive chiamate saranno re<strong>la</strong>tive all'oggetto<br />

TPunto e quindi si cercherà erroneamente di cancel<strong>la</strong>re e disegnare un punto.<br />

Per poter invece realizzare quanto proposto dal POLIMORFISMO, bisognerà fare in modo che i metodi<br />

cancel<strong>la</strong> e disegna siano re<strong>la</strong>tivi all'istanza dell'oggetto chiamante.<br />

E' evidente, allora, che l'indirizzo di salto non può essere fissato rigidamente in fase di compi<strong>la</strong>zione,<br />

ma dovrà essere ricavato successivamente, quando sarà noto l'oggetto chiamante. Ciò è quanto si<br />

definisce un collegamento rinviato (<strong>la</strong>te binding) e per tale motivo i metodi a cui si applica vengono<br />

definiti "dinamici" o virtuali, in opposizione di quelli c<strong>la</strong>ssici che vengono detti "statici", il cui indirizzo è<br />

stabilito immediatamente (early binding).<br />

In termini di sintassi, <strong>la</strong> dichiarazione corretta di tali metodi prevede <strong>la</strong> specifica del<strong>la</strong> direttiva di<br />

compi<strong>la</strong>zione virtual nel<strong>la</strong> definizione del metodo, all'interno del<strong>la</strong> dichiarazione del tipo di oggetto<br />

(NON nell'implementazione).<br />

Inoltre, per realizzare il collegamento rinviato, ovvero il collegamento fra l'istanza e <strong>la</strong> c<strong>la</strong>sse di<br />

appartenenza, all'interno del<strong>la</strong> c<strong>la</strong>sse dovrà essere presente un metodo che consenta, con <strong>la</strong> sua<br />

chiamata, di "costruire" tale collegamento PRIMA che uno qualsiasi dei metodi virtuali vada in<br />

esecuzione. Ciò si ottiene con <strong>la</strong> paro<strong>la</strong> riservata constructor (al posto di procedure). E' pratica comune<br />

scegliere il metodo crea, essendo l'unico che sicuramente sarà sempre chiamato prima dell'utilizzo<br />

dell'oggetto.<br />

type<br />

TPunto = OBJECT<br />

x,y: TCoordinata;<br />

colore: byte;<br />

constructor crea (x0,y0:TCoordinata;col0:byte);<br />

function leggiX: TCoordinata;<br />

procedure cambiaX(nuovaX:TCoordinata);<br />

function leggiY: TCoordinata;<br />

procedure cambiaY(nuovaX:TCoordinata);<br />

function leggiColore: byte;<br />

procedure cambiaColore(nuovoColore:byte);<br />

procedure disegna; virtual;<br />

procedure cancel<strong>la</strong>; virtual;<br />

procedure sposta(nuovaX,nuovaY:TCoordinata);<br />

END;<br />

TCerchio = OBJECT (TPunto)<br />

raggio:integer;<br />

constructor crea (x0,y0:TCoordinata;col0:byte;r0:integer);<br />

procedure disegna; virtual;<br />

procedure cancel<strong>la</strong>; virtual;<br />

function leggiRaggio: integer;<br />

procedure cambiaRaggio(nuovoRaggio:integer);<br />

END;


10<br />

• Ogni tipo di oggetto che possiede dei metodi virtuali deve avere un costruttore.<br />

• La semplice chiamata del costruttore inizializza il collegamento eseguendo un codice nascosto.<br />

Si potrebbe utilizzare anche una procedura vuota, ottenendo lo stesso risultato.<br />

• Dichiarando un metodo come virtual in una c<strong>la</strong>sse, questo sarà tale in tutti i discendenti. A<br />

differenza dei metodi statici, quindi, dovrà necessariamente mantenere invariata l'intestazione.<br />

MECCANISMI CHE IMPLEMENTANO IL POLIMORFISMO<br />

Per ogni CLASSE (TIPO di oggetto) contenente dei metodi virtuali, il compi<strong>la</strong>tore crea nel segmento<br />

dati, una TABELLA DEI METODI VIRTUALI (VMT).<br />

• Esiste una so<strong>la</strong> VMT per tipo di oggetto (e NON una per ogni istanza).<br />

Come <strong>la</strong>scia intuire il nome, <strong>la</strong> VMT contiene gli indirizzi dei metodi virtuali presenti nel<strong>la</strong> c<strong>la</strong>sse.<br />

VMT del<strong>la</strong> c<strong>la</strong>sse TPunto VMT del<strong>la</strong> c<strong>la</strong>sse TCerchio<br />

dimensioni di TPunto<br />

dimensioni di TCerchio<br />

... ...<br />

@TPunto.cancel<strong>la</strong><br />

@TCerchio.cancel<strong>la</strong><br />

@TPunto.disegna<br />

@TCerchio.disegna<br />

... ...<br />

Per ogni ISTANZA di c<strong>la</strong>sse con metodi virtuali, il compi<strong>la</strong>tore alloca nell'area di memoria riservata<br />

all'istanza, un campo supplementare destinato a contenere l'indirizzo (offset) del<strong>la</strong> VMT del<strong>la</strong> c<strong>la</strong>sse<br />

corrispondente.<br />

var punto<br />

x<br />

y<br />

colore<br />

@VMT TPunto<br />

var cerchio<br />

x<br />

y<br />

colore<br />

@VMT TCerchio<br />

raggio<br />

L'indirizzo verrà effettivamente scritto in tale campo quando avverrà <strong>la</strong> chiamata del costruttore,<br />

durante l'esecuzione del programma (e NON durante <strong>la</strong> compi<strong>la</strong>zione).<br />

• Tale caratteristica consente di <strong>la</strong>vorare con <strong>oggetti</strong> il cui tipo è ignoto durante <strong>la</strong> fase di<br />

compi<strong>la</strong>zione, che è <strong>la</strong> vera essenza del polimorfismo.<br />

Essa si basa sul fatto che un discendente eredita, oltre a tutto il resto, anche <strong>la</strong> COMPATIBILITA'<br />

con i suoi antenati, il che equivale a dire che è possibile assegnare ad un antenato uno qualsiasi dei suoi<br />

discendenti.


11<br />

In base a ciò, è del tutto lecito (anche se apparentemente privo di significato) fare un assegnamento<br />

del tipo:<br />

punto:=cerchio;<br />

Dove va a finire il raggio<br />

Più significativo può risultare sfruttare tale compatibilità impiegando<strong>la</strong> nel<strong>la</strong> dichiarazione di un<br />

parametro di una procedura:<br />

procedure <strong>la</strong>mpeggia(var figura:TPunto);<br />

var i:integer;<br />

begin<br />

for i:= 1 to 30 do<br />

begin<br />

figura.cancel<strong>la</strong>;<br />

de<strong>la</strong>y(100);<br />

figura.disegna;<br />

end;<br />

end;<br />

Si noti il passaggio del parametro per riferimento, che consente al parametro attuale, all’atto del<strong>la</strong><br />

chiamata, di essere un punto, un cerchio o addirittura una figura non ancora ideata (purché discendente<br />

da TPunto).<br />

In effetti, i vantaggi del polimorfismo vengono al<strong>la</strong> luce con <strong>oggetti</strong> gestiti tramite il loro indirizzo,<br />

ovvero allocati in memoria dinamica.<br />

type<br />

TPuntoPtr : ^TPunto;<br />

var<br />

puntoPtr:TPuntoPtr;<br />

per consentire di creare un'istanza dinamica, esistono tre possibilità:<br />

• new(puntoPtr);<br />

puntoPtr^.crea(50,70,red);<br />

• new(puntoPtr,crea(50,70,red);<br />

• puntoPtr:=new(TpuntoPtr,crea(50,70,red));<br />

<strong>la</strong> seconda e <strong>la</strong> terza, ideate apposta per <strong>la</strong> OOP, sono da preferire in quanto garantiscono <strong>la</strong> chiamata<br />

del costruttore.<br />

In partico<strong>la</strong>re, <strong>la</strong> terza, essendo una function e utilizzando come primo parametro il type del<br />

puntatore al<strong>la</strong> c<strong>la</strong>sse e non l'istanza , risulta direttamente applicabile senza l'esplicita<br />

dichiarazione di un puntatore (ad esempio, come parametro di una procedura).


12<br />

UN ESEMPIO APPLICATIVO<br />

album<br />

TPunto<br />

TCerchio<br />

type<br />

TNumFigure=1..5;<br />

TFigure = OBJECT<br />

elenco=array[TNumFigure] of TPuntoPtr;<br />

procedure crea;<br />

procedure inserisci(posizione:TNumFigure;figura:TPuntoPtr);<br />

procedure visualizzaTutto;<br />

...<br />

END;<br />

var<br />

album : TFigure;<br />

procedure TFigure.crea;<br />

var i:TNumFigure;<br />

begin<br />

for i:= 1 to High(TNumFigure) do<br />

elenco[i]:= NIL;<br />

end;<br />

procedure TFigure.inserisci(posizione:TNumFigure;figura:TPuntoPtr);<br />

begin<br />

elenco[posizione]:= figura;<br />

end;<br />

procedure TFigure.visualizzaTutto;<br />

var i:TNumFigure;<br />

begin<br />

for i:= 1 to High(TNumFigure) do<br />

if (elenco[i] NIL) then<br />

elenco[i]^.disegna;<br />

end;<br />

BEGIN<br />

...<br />

album.crea;<br />

album.inserisci(1,new(TPuntoPtr, crea(50,50,green)));<br />

album.inserisci(2,new(TCerchioPtr, crea(50,50,25,red)));<br />

...<br />

album.visualizzaTutto;<br />

END.


13<br />

LIBERAZIONE DELLA MEMORIA<br />

Riprendiamo in considerazione il metodo TFigure.inserisci :<br />

Potrebbe capitare di voler sostituire una figura. Dovremmo inserire quindi <strong>la</strong> nuova figura in una<br />

posizione già occupata. In tal caso, per non <strong>la</strong>sciare occupata una parte di memoria dinamica, prima<br />

dell'inserimento dovremmo liberare tale memoria.<br />

La rimozione di un oggetto allocato dinamicamente può avvenire in due modi:<br />

• dispose(puntoPtr);<br />

• dispose(puntoPtr,libera);<br />

La seconda abbina al<strong>la</strong> richiesta di ri<strong>la</strong>scio di un'istanza, <strong>la</strong> chiamata di un suo metodo partico<strong>la</strong>re,<br />

definito destructor.<br />

destructor TPunto.libera;<br />

begin<br />

...<br />

end;<br />

La sintassi estesa del<strong>la</strong> procedura dispose diventa obbligatoria quando l'oggetto da rimuovere è<br />

allocato dinamicamente. In modo partico<strong>la</strong>re, se tale oggetto contiene al suo interno dei riferimenti a<br />

strutture dati o <strong>oggetti</strong> allocati anch'essi dinamicamente, il distruttore si occupa del ri<strong>la</strong>scio ordinato<br />

del<strong>la</strong> memoria occupata da tali risorse. Il distruttore deve quindi essere richiamato immediatamente<br />

prima del<strong>la</strong> distruzione dell'istanza.<br />

I distruttori agiscono solo sugli <strong>oggetti</strong> allocati dinamicamente: oltre a svolgere il <strong>la</strong>voro di "pulizia" (a<br />

carico del programmatore) essi si assicurano che venga ri<strong>la</strong>sciata <strong>la</strong> quantità corretta di memoria per<br />

l'istanza che li invoca (ricavando tale informazione dal<strong>la</strong> VMT). Tale compito viene realizzato da una<br />

parte di codice "nascosta" che il compi<strong>la</strong>tore genera automaticamente in coda al distruttore. Per tale<br />

motivo, il distruttore potrebbe anche essere un metodo vuoto.<br />

Richiedere il ri<strong>la</strong>scio secondo <strong>la</strong> prima modalità, non consente di calco<strong>la</strong>re le esatte dimensioni<br />

dell'istanza: a causa del polimorfismo, un puntatore ad un oggetto potrebbe puntare ad un qualsiasi<br />

discendente.<br />

• La presenza di un distruttore non è legata a quel<strong>la</strong> dei metodi virtuali.<br />

• I distruttori possono essere sia statici che virtuali. Poiché però i diversi tipi di <strong>oggetti</strong><br />

richiedono in genere operazioni di ri<strong>la</strong>scio diverse, è buona norma che essi siano virtuali.


14<br />

TFigure = OBJECT<br />

elenco=array[TNumFigure] of TPuntoPtr;<br />

constructor crea;<br />

destructor libera;virtual;<br />

procedure inserisci(posizione:TNumFigure;figura:TPuntoPtr);<br />

procedure visualizzaTutto;<br />

END;<br />

....<br />

TFigurePtr = ^TFigure;<br />

destructor TFigure.libera;<br />

begin<br />

for i:= 1 to High(TnumFigure) do<br />

if (elenco[i]NIL) then<br />

dispose(elenco[i],libera);<br />

end;<br />

var<br />

albumPtr : TFigurePtr;<br />

BEGIN<br />

albumPtr:=new(TFigurePtr,crea);<br />

albumPtr^.inserisci(1,new(TPuntoPtr, crea(50,50,green)));<br />

albumPtr^.inserisci(2,new(TCerchioPtr, crea(50,50,25,green)));<br />

...<br />

dispose(albumPtr,libera);<br />

END.<br />

L’ultima istruzione chiama il distruttore TFigure.libera che, come illustrato dal codice del metodo,<br />

ri<strong>la</strong>scia <strong>la</strong> memoria occupata da tutte le figure inserite nell’array richiamando il distruttore di ciascuna.<br />

Infine, il codice nascosto di TFigure.libera passa al<strong>la</strong> dispose le dimensioni dell'oggetto puntato da<br />

albumPtr prelevandole dal<strong>la</strong> VMT.

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!