Buono sconto 4% su Toner e Cartucce agli utenti AZpoint. SU Iomiricarico.it!!
|
Si ringrazia Claudio De Sio per la concessione del materiale pubblicato in questo articolo.
|
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));
|