1
|
2 |
3 |
4
Unità didattica 7.1, 7.2 e 7.3.a ) -
Pagina 1
Unità didattica 7.3.b) - Pagina 2
1 - Unità didattica 7.1, 7.2 e 7.3.a )
Un esempio guidato alla
programmazione ad oggetti
Obiettivi:
Il Lettore al termine di questo capitolo dovrà essere in grado di
Sviluppare un’applicazione in Java, utilizzando i paradigmi della
programmazione ad oggetti (unità 7.1, 7.2).
Unità didattica 7.1)
- Perché questo modulo:
Questo modulo è stato introdotto, per dare al lettore una piccola ma
importante esperienza, finalizzata alla corretta utilizzazione dei
paradigmi della programmazione ad oggetti. Quando si approccia all’object
orientation, l’obiettivo più difficile da raggiungere, non è
comprenderne le definizioni, che, come abbiamo visto, sono derivate
dal mondo reale, ma, piuttosto, apprendere il corretto utilizzo di
esse all’interno di un’applicazione. Ciò che sicuramente sembra
mancare al lettore, è la capacità di scrivere un programma, che
sicuramente non è cosa secondaria. Facciamo un esempio: se venisse
richiesto di scrivere un’applicazione che simuli una rubrica, o un
gioco di carte, od un’altra qualsiasi applicazione, il lettore si
porrà ben presto alcune domande: "quali saranno le classi che
faranno parte dell’applicazione?", "dove utilizzare l’ereditarietà",
"dove utilizzare il polimorfismo", e così via. Sono domande cui è
molto difficile rispondere, perché, per ognuna di esse, esistono
tante risposte che sembrano tutte valide. Per esempio, se dovessimo
decidere quali classi comporranno l’applicazione che simuli una
rubrica, potremmo decidere di codificare tre classi (Rubrica,
Persona, e classe che contiene il main), oppure 5 classi (Indirizzo,
Ricerca, Rubrica, Persona, e classe che contiene il main).
Riflessioni approfondite sulle varie situazioni che si potrebbero
presentare nell’utilizzare quest’applicazione (casi d’uso),
probabilmente consiglierebbero la codifica di altre classi. Una
soluzione con molte classi sarebbe probabilmente più funzionale, ma
richiederebbe troppo sforzo implementativo per un’applicazione
definibile "semplice". D’altronde, un’implementazione che fa uso di
un numero ridotto di classi, costringerebbe lo sviluppatore, ad
inserire troppo codice in troppo poche classi. Inoltre, ciò
garantirebbe in misura minore l’utilizzo dei paradigmi della
programmazione ad oggetti, giacché la nostra applicazione, più che
simulare la realtà, cercherà di "arrangiarla". La soluzione migliore
sarà assolutamente personale, perché garantita dal buon senso e
dall’esperienza. È già stato accennato che un importante supporto
alla risoluzione di tali problemi, è garantito dalla conoscenza di
metodologie object oriented, o, almeno dalla conoscenza di U.M.L..
In questa sede però, dove il nostro obbiettivo principale è quello
di apprendere un linguaggio di programmazione, non è consigliabile,
né fattibile, introdurre anche altri argomenti tanto complessi. Sarà
presentato invece un esercizio-esempio, che il lettore può provare a
risolvere, oppure direttamente leggerne la soluzione presentata. Con
quest’esercizio ci poniamo lo scopo di operare delle attente
osservazioni sulle scelte fatte, per poi andare a trarre delle
conclusioni importanti. Inoltre, la soluzione sarà presentata, con
una filosofia iterativa ed incrementale, così com’è stata creata,
sul modello delle moderne metodologie orientate agli oggetti. In
questo modo, saranno esposti al lettore tutti i passaggi eseguiti,
per arrivare alla soluzione.
Unità didattica 7.2)
- Esercizio 7.a)
N.B.: con quest’esercizio non realizzeremo un’applicazione utile, lo
scopo è puramente didattico.
Obiettivo:
Realizzare un‘applicazione che possa calcolare la distanza
geometrica tra punti. I punti possono trovarsi su riferimenti a due
o tre dimensioni.
Unità didattica 7.3)
- Risoluzione dell’esercizio 7.a)
Di seguito è presentata una delle tante possibili
soluzioni. Non bisogna considerarla come una soluzione da imitare,
ma piuttosto come elemento di studio e riflessione per applicazioni
future.
- Passo 1:
Individuiamo le classi di cui sicuramente l’applicazione non può
fare a meno. Sembra evidente che componenti essenziali debbano
essere le classi che devono costituire il "modello" di quest’applicazione.
Codifichiamo le classi Punto e Punto3D sfruttando incapsulamento,
ereditarietà, overload di costruttori, e riutilizzo del codice:
class Punto
{
private int x, y;
public Punto()
{ //Costruttore senza parametri
}
public Punto(int x, int y)
{
this.setXY(x, y); //Il this è facoltativo
//riutilizziamo codice
}
public void setX(int x)
{
this.x=x; //Il this è facoltativo
}
public void setY(int y)
{
this.y=y; //Il this è facoltativo
}
public void setXY(int x, int y)
{
this.setX(x); //Il this è facoltativo
this.setY(y);
}
public int getX()
{
return this.x; //Il this è facoltativo
}
public int getY()
{
return this.y; //Il this è facoltativo
}
}
class Punto3D extends Punto
{
private int z;
public Punto3D()
{ //Costruttore senza parametri
}
public Punto3D(int x, int y, int z)
{
this.setXYZ(x, y, z); //Il this è facoltativo
//riutilizziamo codice
}
public void setZ(int z)
{
this.z=z; //Il this è facoltativo
}
public void setXYZ(int x, int y, int z)
{
this.setXY(x, y); //Il this è facoltativo
this.setZ(z);
}
public int getZ()
{
return this.z; //Il this è facoltativo
}
}
N.B.: notiamo che pur di utilizzare
l’ereditarietà "legalmente", abbiamo violato la regola
dell’astrazione. Infatti, abbiamo assegnato l’identificatore Punto
ad una classe che si sarebbe dovuta chiamare Punto2D. Ricordiamo che
per implementare il meccanismo dell’ereditarietà, lo sviluppatore
deve testarne la validità, mediante la cosiddetta "is a"
relationship (la relazione "è un"). Violando l’astrazione abbiamo
potuto validare l’ereditarietà: ci siamo infatti chiesti: "un punto
a tre dimensioni è un punto?". La risposta affermativa ci ha
consentito la specializzazione. Se avessimo rispettato la regola
dell’astrazione non avremmo potuto implementare l’ereditarietà tra
queste classi, dal momento che, ci saremmo dovuti chiedere: "un
punto a tre dimensioni è un punto a due dimensioni?". In questo caso
la risposta sarebbe stata negativa. Questa scelta è stata fatta non
perché ci faciliti il prosieguo dell’applicazione, anzi, proprio per
osservare che in un’applicazione che subisce degli incrementi, il
"violare le regole" porta a conseguenze negative. Nonostante tutto,
la semplicità del problema e la potenza del linguaggio, ci
consentiranno di portare a termine il nostro compito.
Contemporaneamente vedremo quindi come forzare il nostro codice
affinché soddisfi i requisiti.
N.B.: la codifica di due classi come queste è stata ottenuta
apportando diverse modifiche. Il lettore non immagini di ottenere
soluzioni ottimali al primo tentativo! Questa osservazione varrà
anche relativamente alle codifiche realizzate nei prossimi passi.
- Passo 2:
Individuiamo le funzionalità del sistema. È stato richiesto che la
nostra applicazione debba in qualche modo calcolare la distanza tra
due punti. Facciamo alcune riflessioni prima di "buttarci sul
codice". Distinguiamo due tipi di calcolo della distanza tra due
punti: tra due punti bidimensionali, e tra due punti
tridimensionali. Escludiamo a priori la possibilità di calcolare la
distanza tra due punti, di cui uno è bidimensionale e l’altro e
tridimensionale. A questo punto sembra sensato introdurre una nuova
classe che abbia la responsabilità di eseguire questi due tipi di
calcoli. Nonostante questa appaia la soluzione più giusta, optiamo
per un’altra strategia implementativa: assegniamo alle stesse classi
Punto e Punto3D la responsabilità di calcolare le distanze relative
ai "propri" oggetti. Notiamo che l’astrarre queste classi inserendo
nelle loro definizioni dei metodi chiamati dammiDistanza(),
rappresenta una palese violazione alla regola dell’astrazione
stessa. Infatti, in questo modo potremmo affermare che intendiamo un
oggetto come un punto, come capace di "calcolarsi da solo" la
distanza geometrica che lo separa da un altro punto. E tutto ciò non
rappresenta affatto una situazione reale.
Quest’ulteriore violazione dell’astrazione di queste classi, ci
permetterà di valutarne le conseguenze, e, contemporaneamente, di
verificare la potenza e la coerenza della programmazione ad oggetti.
N.B.: la distanza geometrica tra due punti a due dimensioni, è data
dalla radice della somma del quadrato della differenza tra la prima
coordinata del primo punto e la prima coordinata del secondo punto,
e del quadrato della differenza tra la seconda coordinata del primo
punto e la seconda coordinata del secondo punto.
Di seguito è presentata la nuova codifica della classe Punto, che
dovrebbe poi essere estesa dalla classe Punto3D:
class Punto
{
private int x, y;
. . . //inutile riscrivere l’intera classe
public double dammiDistanza(Punto p)
{
int tmp1=(x-p.x)*(x-p.x);
//quadrato della differenza della x dei due punti
int tmp2=(y-p.y)*(y-p.y);
//quadrato della differenza della y dei due punti
return Math.sqrt(tmp1+tmp2);
//radice quadrata della somma dei due quadrati
}
}
Notiamo come in un eventuale metodo main sarebbe possibile scrivere
il seguente frammento codice:
Punto p1 = new Punto(5,6);
Punto p2 = new Punto(10,20);
System.out.println("distanza è" +p1.dammiDistanza(p2));
Unità didattica 7.1, 7.2 e 7.3.a ) -
Pagina 1
Unità didattica 7.3.b) - Pagina 2