la programmazione orientata agli oggetti - itis magistri cumacini
la programmazione orientata agli oggetti - itis magistri cumacini
la programmazione orientata agli oggetti - itis magistri cumacini
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
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.