7. Java e la progettazione ad oggetti 7.1. Linguaggio e fasi di

appunti java
pag. 47
7. Java e la progettazione ad oggetti
Nei paragrafi precedenti si è mostrato che una classe di Java è la veste che
assume una pluralità di cose:
• programmi standalone;
• applet;
• classi che definiscono nuovi “oggetti”.
In generale per passare dalla programmazione imperativa-procedurale del Pascal
alla programmazione ad oggetti in Java è necessario “ricostruire” un quadro di
riferimento concettuale “nuovo”.
In Pascal si ragiona in termini di Algoritmi e di Strutture Dati, come indicato fin
dalle origini dal “progettista” del Pascal Niklaus Wirth che intitola un suo testo
fondamentale
Programmi=Algoritmi+StruttureDati. Nella programmazione ad
oggetti si potrebbe dire che un Programma è una Costruzione “tipo Lego” e i
mattoni che la compongono sono gli “Oggetti”.
Un oggetto (più esattamente una classe) è un “contenitore” costituito dai Dati che
lo caratterizzano e dalle Operazioni che intervengono su quei dati. In termini più
precisi una classe è un’entità unica costituita da tre parti essenziali:
• NOME (che individua la classe univocamente)
• ATTRIBUTI o CAMPI (Dati che caratterizzano la classe)
• METODI (Servizi forniti dalla classe o Operazioni che intervengono sugli
attributi).
7.1.
Linguaggio e fasi di progettazione
In precedenza si sono utilizzate alcune classi presenti nel "jdk" di java quali
String, StringBuffer, Integer o Double e si è visto il manuale della
documentazione di Java e la sua organizzazione in "Packages" che sono
raggruppamenti di classi affini.
Ora si cercheranno di definire alcuni criteri necessari per progettare un
programma con una metodologia orientata agli oggetti Object Oriented (OO).
Di norma si parte da una Situazione Problematica (situazione più o meno definita
dalle esigenze dell'utente finale) e si affrontano diverse fasi che, in sequenza,
sono: l'Analisi, il Disegno e la vera e propria Programmazione.
FASI
DOCUMENTI
Analisi preliminare
Def. CASI D'USO
Disegno o specifica
Disegno CLASSI
Codifica e Test
CODICE Java
appunti java
pag. 48
a) Nella fase di analisi OOA si analizza la situazione problematica e si
definiscono i "casi d'uso" finali, i requisiti, le funzionalità richieste
dall'utente. Occorre porsi dal punto di vista dell'utilizzatore finale e usare le
conoscenze di sistema che di norma sono spesso esterne all’ambito
dell'informatica .
Ai casi d'uso individuati si assegna un nome e si schematizza la situazione
con il diagramma dei casi d'uso, eventualmente accompagnato da un testo
esplicativo.
b) Nella fase di disegno OOD, che segue l'individuazione dei casi d'uso, si
devono individuare le classi (o le gerarchie di classi) necessarie,
rispondendo alla domanda "chi fa che cosa?". Questa domanda può essere
tradotta come "quali Classi devono fornire i servizi necessari per realizzare
le funzionalità individuate nei casi d'uso?"
• Si decidono le caratteristiche informative, il tipo di dati e le
caratteristiche relazionali che definiscono gli attributi (campi o data
member) della classe.
• Si individuano quali servizi quelle classi devono fornire. Ovvero quali
operazioni (metodi) sono necessarie per “manipolare” un oggetto di
quella classe. Si definiscono i costruttori della classe. Si individuano le
relazioni tra le classi progettate. Sotto è disegnato lo schema di
rappresentazione di un Classe.
<Nome>
<Attributi>
<Costruttori>
<Metodi>
c) Nella fase di programmazione o codifica OOP, che segue il disegno delle
classi, si produce la codifica vera e propria delle classi e dei metodi
individuati e si testa con un programma di prova il funzionamento dei casi
d'uso desiderati.
appunti java
7.2.
pag. 49
Esempi di progettazione
Supponiamo ad esempio di voler affrontare la semplice situazione problematica
seguente:
esempio 1 - "Si desidera realizzare un sistema che manipoli cerchi
calcolandone l'area e determinando se un punto assegnato è interno
o esterno al cerchio."
Nella fase di analisi (OOA) si individueranno le esigenze dell'utente che si
possono desumere dalle parole chiave del testo:
• possibilità di inserire i dati che definiscono un cerchio;
• possibilità di calcolare l'area:
• possibilità di sapere se un punto assegnato è interno al cerchio.
Questi sono i tre casi d'uso che possiamo stabilire in prima approssimazione e
schematizzare con il seguente diagramma:
immetti
area
interno
Nella fase di Disegno (OOD) si dovranno definire le classi necessarie CHE FANNO
ciò che si desidera (CHI FA CHE COSA?).
Per il nostro problema la classe centrale sarà il cerchio e dovrà avere come
attributi raggio e centro. I metodi minimi necessari saranno:
• il costruttore che consente l'immissione dei dati;
• il metodo Area;
• il metodo interno.
Lo schema da realizzare per disegnare tale classe è il seguente:
Cerchio
- double Xc;
- double Yc;
- double raggio;
+ Cerchio(double x, double y, double r)
+ area() : double;
+ interno(double x, double y) : boolean;
Si potrebbero progettate non una ma due classi: la Cerchio, indispensabile, e una
classe Punto che potrebbe indicare il fatto che il cerchio è dotato di un punto
particolare che è il suo centro.
appunti java
pag. 50
In questo modo il disegno alternativo sarebbe:
Cerchio
Punto
- double Xc;
- double Yc;
+ Punto(double x, double y)
- Punto centro;
- double raggio;
associazione
1
+ Cerchio(Punto p, double r)
+ area() : double;
+ interno(Punto p) : boolean;
+ getX( ) : double;
+ getY( ) : double;
Le due alternative di disegno sono didatticamente utili per mostrare come si
rappresenta una relazione tra due diverse classi. E' evidente che se si progetta
una classe Punto si deve prevedere un costruttore adeguato e che per accedere
dall'esterno agli attributi privati del Punto dovranno essere disegnati i due metodi
getX() e getY().
Annotazioni teoriche:
Modalità con cui si indicano attributi, costruttori e metodi. Il segno che li precede
(-), (+), (#) indica rispettivamente che il metodo è private, public o protected. Un
metodo o un attributo public (+) è SEMPRE accessibile dall'esterno della classe,
un metodo o un attributo private (-) è accessibile SOLO dal codice interno alla
classe; infine un metodo o un attributo protected (#) è accessibile solo dal codice
della classe o delle sue sottoclassi ma non dall'esterno.
Le classi possono stare tra loro in una relazione associativa di Composizione e
questo si indica con la seguente simbologia
Punto
Punto
1
3..*
Cerchio
Poligono
due classi sono in relazione di composizione se la prima di esse risponde alla
domanda "HA UN" e la seconda di conseguenza si configura come la parte di un
tutto. Nel primo caso disegnato si dice che la classe Cerchio ha un Punto (il
centro) che le appartiene come sua parte. Nel secondo caso disegnato la relazione
ci informa del fatto che un Poligono deve avere come sue parti 3 o più punti.
Sempre nella fase di Disegno (ma potrebbe essere realizzata
contemporaneamente alla codifica) si deve definire la Documentazione della
classe; questa consiste nello scrivere le specifiche o manuale d'uso di ogni metodo
della classe. Tali Specifiche hanno valore contrattuale e formale, devono quindi
essere precise e non contenere ambiguità. Il documento di specifica è utilizzato,
quando si progetta in grande, da due diverse figure: Il programmatore di sistema, il
quale costruisce il pacchetto con le nuove classi vincolandosi alle specifiche, e il
appunti java
pag. 51
futuro utilizzatore, il quale userà le specifiche come manuale della classe per
realizzare applicazioni.
Per queste finalità tale manuale dovrà contenere una "intestazione" dei metodi
(detta anche signature) scritta nella sintassi del linguaggio di programmazione e
la descrizione per ogni metodo di:
• effetti prodotti dal metodo;
• descrizione dei parametri di input e di output;
• casi d'uso ed eventuali eccezioni.
Disegno o specifica
Classi e Relazioni tra esse
Metodi e significato dei parametri
Produzione del
manuale
Eccezioni ed esempi d’uso
Nome
Campi o attributi
Costruttori
Identificatore
Punto centro
double raggio
Intestazione:
Cerchio
private
Invocazione:
Intestazione:
Punto p=new Punto(3,4);
Cerchio C=new Cerchio(p, 9);
Riceve il centro p di coordinate (3,4) e il raggio 9 e
costruisce un oggetto cerchio C.
double area()
Invocazione:
double S=C.area();
Effetti::
Intestazione:
determina l'area del cerchio C e l'assegna alla
variabile S.
boolean interno(Punto p)
Invocazione:
boolean b=C.interno(p);
Effetti::
determina se il punto p assegnato è o no interno
al cerchio C. Restituisce true se Si, No altrimenti.
Effetti:
Metodi
Cerchio(Punto p, double r)
Nella fase di codifica (OOP) delle Classi e dei metodi (attività specifica del
programmatore di sistema) si deve scegliere l’algoritmo più opportuno per
implementare ciascun metodo. Di norma questa fase è lasciata libera da vincoli
sia nella scelta delle strutture dati di supporto più opportune che nella scelta
dell’algoritmo; ovviamente tale scelta deve superare i test di funzionamento
previsti dalle specifiche e gli eventuali test di efficienza (la velocità di esecuzione
dipende dalla rappresentazione dei dati e dall’algoritmo scelti).
appunti java
pag. 52
Codifica e test
Codifica Classi
Scelta algoritmi e codifica metodi
CODICE Java
Test di correttezza ed efficienza
Nel caso del problema da cui siamo partiti si tratterà di codificare le due Classi
individuate nel disegno e di costruire un programma di prova che verifichi il
funzionamento dei Casi d'Uso previsti.
public class Punto {
private double Xc;
private double Yc;
public Punto(double x, double y) {
Xc=x; Yc=y;
}
public double getX() {
return Xc;
}
public double getY() {
return Yc;
}
} // end Punto
public class Cerchio {
private Punto centro;
private double raggio;
public Cerchio(Punto p, double r) {
centro=p; raggio=r;
}
public double area() {
double ris=Math.PI*raggio*raggio;
return ris;
}
public boolean interno(Punto p) {
boolean ris=false;
double x=p.getX();
double y=p.getY();
double xc=centro.getX();
double yc=centro.getY();
double dist=Math.sqrt((x-xc)*(x-xc)+(y-yc)*(y-yc));
if (dist<=raggio) ris=true;
return ris;
}
} // end Cerchio
Il main() deve verificare i "casi d'uso" individuati nell'analisi, in particolare
calcolare l'area e stabilire se un punto assegnato è o no interno al cerchio:
appunti java
pag. 53
public class es01_c07 {
public static void main(String arg[]) {
Punto Centro=new Punto(3,4);
Cerchio C=new Cerchio(Centro, 9);
Punto P=new Punto(3,13);
Punto Q=new Punto(4,10);
Punto R=new Punto(5,13);
System.out.println("Area="+C.area());
System.out.println("P interno "+C.interno(P)+", Q interno "
C.interno(Q) +", R interno "+C.interno(R));
}
}
+
Il programma stamperà:
Area=254.46900494077323
P interno true, Q interno true, R interno false
Supponiamo ad esempio di voler affrontare la situazione problematica
seguente:
esempio 2 - "Si desidera realizzare un programma che manipoli numeri
Razionali (frazioni) e si vuole almeno la funzionalità di
immissione e stampa". In un secondo momento si amplierà il sistema con altre
operazioni.
Analisi preliminare (definizione dei Casi d'Uso)
Lo schema indica i due casi d'uso indicati nel testo:
immetti
stampa
Disegno o specifica
Un numero razionale è una coppia di numeri (n,d) tali che n ε Z e d ε N0, il
numeratore potrebbe avere segno negativo o essere nullo, il denominatore deve
essere diverso da Zero. Questi sono gli unici due attributi previsti per la classe
razionale. Le uniche operazioni che si svilupperanno provvisoriamente sono
l’operazione che consente di immettere “costruire” un razionale e quella che
consente di “stamparlo” sul video.
Il numeratore e il denominatore saranno rappresentati (memorizzati) in una
forma "normalizzata" nel senso che è ammesso il segno negativo al più al
numeratore, e i due numeri devono essere ridotti ai minimi termini. Se si
costruisce il razionale –4/8 si memorizzerà negli attributi –1/2. A questo scopo si
è previsto di progettare un metodo (private) che determina il massimo comune
divisore al fine di semplificare i valori immessi dall'utente.
appunti java
pag. 54
Lo schema di disegno della classe è il seguente:
- long N;
- long D;
Razionale
+ Razionale(long n, long d)
+ stampa( ) : void;
- mcd(long a, long b) : long;
Il disegno si conclude con la specifica che consiste nella scrittura del manuale.
Nome
Campi o attributi
Costruttori
Metodi
Razionale
private
Identificatore
int Num;
int Den;
Intestazione::
Razionale(long Num, long Den)
Invocazione:
Razionale r=new Razionale(6,-4);
Effetti:
Intestazione:
Riceve 6 e -4 interi e alloca l’oggetto r = –3/2;
se il denominatore è zero interrompe e segnala un
errore irreparabile.
void stampa()
Invocazione::
r.stampa();
Effetti::
Riceve il razionale r e lo mostra a video in forma
frazionaria ridotta ai minimi termini. Se il
denominatore è 1 stampa il solo numeratore.
Il jdk di Java mette a disposizione uno strumento (programma javadoc) che
consente di stilare, oltre al codice, il testo del manuale d'uso delle classi codificate
nella forma di file HTML. In questo caso si parla di "manuale on line" del sistema
progettato.
In particolare codificando le classi e i metodi con opportuni commenti formali e
"lanciando" il programma javadoc si ottiene automaticamente la documentazione
in formato <HTML>.
Per l'esempio "Razionale" si farà uso di tale potenzialità. Di seguito vediamo i
passi da percorrere, documentazione inclusa, in sede di codifica.
Codifica, Test e manuale online
La codifica di questa classe in Java dovrà rispettare le specifiche e memorizzare
due numeri ridotti ai minimi termini e “normalizzati” nel segno. I commenti
formalizzati compresi tra /** … */ sono indispensabili per ottenere il manuale di
documentazione online.
appunti java
pag. 55
/**
* La classe Razionale consente di immettere e stampare numeri razionali.
* @author Pinco Pallo 2 Luglio 2002
*/
public class Razionale {
private long N,D;
//ATTRIBUTI della classe
/**
* Costruttore di Razionale.
* @param a numeratore, b denominatore
* <p>Se il parametro (denominatore) b è nullo, si ha una
* interruzione con un messaggio di fallimento.</p>
*/
public Razionale(long a, long b) {
long div = mcd(a,b);
a=a/div; b=b/div;
if (b<0) {N=-a; D=-b;}
else {N=a; D=b;}
}
/**
* Stampa un Razionale nella forma n/d.
* @return void
*/
public void stampa() {
if (D==1) System.out.println(N);
else System.out.println(N+"/"+D);
}
private static long mcd(long a, long b) { // mcd() Privato
a=Math.abs(a); b=Math.abs(b);
long ris=0;
if (b!=0) {
if (a!=0) {
// a e b diversi da zero
long r=1;
while (r!=0) {
r= a % b;
a=b; b=r;
}
ris=a;
}
else ris=b;
// a=0, b diverso da zero
}
else {
// b=0 inaccettabile
System.out.println("Denominatore Nullo! errore fatale.");
System.exit(1);
}
return ris;
}
}
Si salva il file sorgente col nome Razionale.java e dopo la compilazione si ottiene il
programma oggetto Razionale.class.
Per ottenere la documentazione online sotto la forma di file HTML è necessario
scegliere dal menù di RealJava l'opzione <Tools> e successivamente <Javadoc
Project Files..>. Dopo aver scelto la cartella nella quale salvare la documentazione
si avvia il processo di generazione automatica della documentazione.
Al termine, se non vengono segnalati errori, si apre la cartella e si avvia
<index.html> visualizzando la documentazione di Razionale.
Nella scrittura della documentazione sono da notare i TAG specifici che
compaiono tra /**…*/ preceduti da @; si tratta di TAG predefiniti quali @author
(per definire l'autore del progetto) @param (per illustrare il significato dei
appunti java
pag. 56
parametri passati al metodo) e @return (che illustra i parametri restituiti da un
metodo. Per vedere gli altri TAG disponibili leggere la documentazione di Java
nella illustrazione di Javadoc.
Per fare un test o usare la classe e costruire oggetti razionali è necessario creare
un main program di prova come il seguente:
class es02_c07 {
public static void main(String args[]) {
Razionale a, b, c;
a=new Razionale(-3,-6);
b=new Razionale(28,-8);
c=new Razionale(28,-7);
a.stampa();
b.stampa();
c.stampa();
}
}
eseguendo l’operazione Run si ottiene come risultato:
1/2
-7/2
-4
In sintesi analizzando il main() si nota che:
•
•
Razionale non è un tipo di dato statico come int o float ma un oggetto. Lo si
deduce dal fatto che la definizione Razionale a; non è sufficiente per disporre
di un dato di tipo razionale ma è necessario “crearlo” con il costruttore
Razionale(). L’operazione a=new Razionale(num,den); provvede a semplificare
la frazione e a memorizzarla “allocandola dinamicamente” nella memoria libera
del computer. Ogni invocazione del costruttore crea (istanzia) un oggetto.
L’invocazione di un metodo avviene sempre anteponendo all’operazione
l’oggetto a cui deve essere applicato quel metodo. La scrittura a.stampa(); si
potrebbe interpretare come l’ordine “applica l’operazione stampa all’oggetto a”.
Questa sintassi è tipica dell’invocazione dei metodi applicati agli oggetti.
Supponiamo ad esempio di voler ampliare le richieste definendo la seguente
situazione problematica:
esempio 3 - "Si desidera ampliare la classe razionale implementando
almeno l'operazione di somma tra due razionali."
Trascuriamo di disegnare i "casi d'uso" della nuova funzionalità e limitiamoci a
indicare nel disegno della classe il nuovo metodo sum() che esegua la somma di
due frazioni nel modo seguente:
Nome
Metodi
Identificatore
Intestazione:
Razionale
Razionale sum(Razionale B)
Invocazione::
Razionale C=A.sum(B);
Effetti::
Riceve il Razionale B e lo somma a quello
invocante A. Il risultato Razionale restituito è
assegnato alla variabile C.
appunti java
pag. 57
public Razionale sum(Razionale r) {
long nris, dris;
nris = N*r.D+D*r.N;
dris = D*r.D;
return new Razionale(nris,dris);
// METODO sum()
// le variabli N e D
// non vengono definite
// perché sono attributi.
}
modificando il main() si può eseguire un test nel modo seguente:
class es03_c07 {
public static void main(String args[]) {
Razionale a, b, c, d;
a=new Razionale(-3,-6);
b=new Razionale(28,-8);
c=new Razionale(28,-7);
a.stampa();
b.stampa();
c.stampa();
d=a.sum(b);
d.stampa();
}
}
Si ottiene a video:
1/2
-7/2
-4
-3
Spingiamoci oltre affrontando la seguente situazione problematica:
esempio 4 - "Si desidera implementare una <finestra di Windows> che
consenta alcune operazioni: almeno la creazione, il disegno sul
desktop e il suo dimensionamento."
OOA
Crea_frame
Dimensiona
Disegna
appunti java
pag. 58
OOD
La classe potrebbe avere il seguente disegno
Frame
- int x, y;
- int width, height;
- boolean stato;
+ Frame()
+ setSize(int W, int H ) : void;
+ setBounds(int X, int Y, int W, int H ) : void;
+ setVisible(boolean S ) : void;
x,y sono le coordinate in alto a sinistra della frame; width, height la sua
larghezza e altezza espresse in pixel, lo stato indica se la frame è visibile sullo
schermo; stato true o se e solo allocata nella memoria; stato false.
Le specifiche del manuale potrebbero essere:
Nome
Campi o attributi
Costruttori
Metodi
•
Identificatore
int x, y;
int width, height;
Frame
private
boolean visible;
Intestazione::
Frame()
Invocazione:
Frame f=new Frame();
Effetti:
Intestazione:
Crea in memoria la struttura (non visibile su
schermo) con dimensioni nulle.
void setSize(int W, int H);
Invocazione::
f.setSize(100, 100);
Effetti::
Intestazione:
Dimensiona f assegnando agli attributi width=100
ed height=100
void setBounds(int X, int Y, int W, int H);
Invocazione::
f.setBounds(20,30, 100, 100);
Effetti::
Intestazione:
Posizione il vertice in alto a sinistra di f in (20,30)
rispetto al desktop e assegna agli attributi
width=100 ed height=100
void setVisible(boolean stato);
Invocazione::
f.setVisible(true);
Effetti::
Mostra sul desktop la frame f.
OOP
La class Frame per fortuna, è già stata implementata dai progettisti di java e
quindi possiamo limitarci a realizzare un programma di prova che mostri i casi
d'uso che abbiamo definito nell'analisi.
appunti java
pag. 59
Si noti che la classe Frame e tutti gli oggetti grafici sono contenuti nel Package
java.awt. e quindi per utilizzarli in un programma è necessario "importare" il
package che la contiene, come si vede nella prima riga del codice seguente.
import java.awt.*;
class es04_c07 {
public static void main(String args[]) {
Frame f=new Frame();
f.setBounds(200,150,100,100);
Frame g=new Frame();
g.setSize(200,100);
f.setVisible(true);
g.setVisible(true);
}
}
Se provate ad eseguire questo programma otterrete sulla finestra dell’editor la
seguente situazione:
E’ evidente che la classe Frame è stata implementata nel linguaggio Java e quindi
tutto funziona. Noterete che le Frame create non si chiudono, l’unico modo di
chiuderle è spegnere il computer o interrompere il processo Java, non ci siamo
preoccupati di attivare un METODO che consenta di terminare il programma.
Vedremo successivamente come fare.
Si potrebbe affrontare un problema di progettazione di carattere gestionale come
il seguente.
esempio 5 - "Si desidera gestire un Conto Corrente bancario
consentendo di eseguire operazioni di creazione, prelievo e
versamento; inoltre si desidera anche registrare il numero di
operazioni eseguite sul conto. Un cliente della banca può avere al
massimo due conti correnti mentre un conto corrente ha sempre un
solo titolare."
• OOA
I Casi d'Uso identificati sono:
appunti java
pag. 60
Crea_Cliente
Crea_CC
Versa_Preleva
Mostra
• OOD
Le classi da progettare sono la Conto Corrente e la Cliente. E' opportuno
progettare una classe Cliente per evitare la ripetizione dei dati anagrafici del
Cliente nei due possibili conti correnti con lo stesso intestatario che il problema
autorizza. Se i dati del cliente fossero inseriti come attributi di ogni CC si
ripeterebbero inutilmente inserimenti e aggiornamenti con la quasi sicurezza di
inserire incongruenze.
E' evidente che le due classi devono interagire tra loro e quindi dobbiamo
immaginare che tra esse ci sia una relazione associativa, infatti un CC ha
sempre un Cliente e un Cliente può avere da Zero a Due Conti.
L'associazione risponde ancora alla modalità "HA UN", infatti un CC ha sempre
un cliente intestatario.
Si può notare che questa associazione non è esattamente corrispondente a quella
che intercorreva tra Cerchio e Punto, infatti un "oggetto" Cerchio "ha come sua
parte obbligatoriamente un punto" questo implica che se si elimina (cancella dalla
memoria) il cerchio si elimina anche il suo centro associato.
Se è vero che un "oggetto" CC ha sempre come associato un "oggetto" Cliente NON
è' vero che eliminando un CC deve essere eliminato anche il Cliente. Il cliente può
avere un altro Conto, il Cliente non è a rigore "UN COMPONENTE" del CC.
La relazione associativa si chiama in questo contesto Aggregazione.
La rappresentazione grafica può essere:
CC
-
int NumConto;
double Saldo;
int NumOp;
Cliente Cli;
+ CC(Cliente inte, int numc)
+
+
+
+
versa(double cifra ) : void;
preleva(double cifra ) : boolean;
mostraCli( ) : void;
mostra( ) : void;
Cliente
0..2
1
-
String Nome;
String Indir;
String Tel;
CC C1=null; C2=null;
+ Cliente( String nom, String in,
String te)
+ setConto( CC c): void;
+ mostra( ) : void;
appunti java
pag. 61
Il rombo vuoto ( in Composizione era pieno) indica che si tratta di Aggregazione
e la direzione della freccia indica che la "navigabilità" va da CC a Cliente ovvero
che si accede ai servizi di cliente partendo da CC e non viceversa. CC è
responsabile del sistema mentre Cliente si limita a fornire a CC gli attributi
anagrafici. Non è possibile accedere a CC partendo da Cliente. Questa
interpretazione non consente di controllare, con facilità, che un Cliente abbia un
massimo di due CC intestati come richiesto dal testo.
Il sistema delle due classi può essere progettato anche in modo diverso
immaginando che Cliente sia responsabile del sistema e che un Cliente "Abbia
come suoi componenti" da 0 a 2 CC e che tutte le funzionalità di CC siano
"navigabili" a partire da Cliente. In questo caso si realizza un'associazione di
Composizione perché è legittimo pensare che un Cliente che viene eliminato
comporti anche la chiusura dei conti di sua proprietà. In questo modo si controlla
anche che il cliente non superi mai i due conti a lui intestati.
Il diagramma della classi cambia anche per i servizi che ciascuna classe fornirà.
Cliente
CC
-
- int NumConto;
- double Saldo;
- int NumOp;
0..2
String Nome;
String Indir;
String Tel;
CC C1=null, C2=null;
+ Cliente( String nom, String in,
String te)
+ CC( int numc)
+
+
+
+
+
+ versa(double cifra ) : void;
+ preleva(double cifra ) : boolean;
+ mostra( ) : void;
creaConto(int numc): boolean;
contoUno() : CC;
contoDue() : CC;
mostraCC( ) : void;
mostra( ) : void;
Si potrebbe pensare che il sistema debba consentire "navigabilità" nei due sensi a
seconda che il sistema debba fornire servizi d'ufficio che prendono in esame totali
e statistiche che partono dai Conti Correnti o servizi di sportello che si limitano
ad ascoltare le richieste dei correntisti. In questo caso si evidenzia la doppia
navigabilità e la relazione con le rispettive molteplicità. Il progetto delle classi
potrebbe essere indicato nel seguente modo
Cliente
-
CC
-
int NumConto;
double Saldo;
int NumOp;
Cliente Cli;
+ CC(Cliente inte, int numc)
+
+
+
+
versa(double cifra ) : void;
preleva(double cifra ) : boolean;
mostraCli( ) : void;
mostra( ) : void;
0..2
1
String Nome;
String Indir;
String Tel;
CC C1=null, C2=null;
+ Cliente( String nom, String in,
String te)
+ creaConto(int numc): boolean;
+setConto(CC c) : void;
+ contoUno() : CC;
+ contoDue() : CC;
+ mostraCC( ) : void;
+ mostra( ) : void;
appunti java
•
pag. 62
OOP
La codifica del primo dei tre progetti sarà:
public class CC {
private
private
private
private
// Classe Conto Corrente
int NumConto;
double Saldo;
int NumOp;
Cliente Cli=null;
public CC(Cliente inte,int numc){
inte.setConto(this); Cli=inte;
NumConto=numc; Saldo=0;NumOp=0;
}
public void versa(double cifra){
Saldo=Saldo+cifra;
NumOp++;
}
public boolean preleva(double cifra){
boolean fatto=true;
if (cifra<=Saldo) {
Saldo=Saldo-cifra;
NumOp++;
}
else {
System.out.println("Saldo insufficiente! OP Fallita.");
fatto=false;
}
return fatto;
}
public void mostraCli() {
Cli.mostra();
}
public void mostra( ){
System.out.println("Conto n° "+NumConto);
System.out.println("Opreazioni svolte= "+NumOp);
System.out.println("Saldo= "+Saldo+"\n");
}
}
public class Cliente {
// class Cliente
private String Nome;
private String Indir;
private String Tel;
private CC C1=null, C2=null;
public Cliente(String nom,String ind, String tel){
Nome=nom; Indir=ind; Tel=tel;
}
appunti java
pag. 63
public void setConto(CC c) {
if (C1==null) C1=c;
else
if (C2==null) C2=c;
else {
System.out.println("il cliente ha giù due conti. op Fallita!");
System.exit(0);
}
}
public void mostra( ){
System.out.println("Cliente :"+Nome);
System.out.println("Indirizzo :"+Indir+"
}
}
Telefono: "+Tel+"\n");
Il main() che verifica i casi d'uso sarà;
public class es05_c07 {
public static void main(String ar[]){
Cliente C1=new Cliente("Tizio","via Pioppa 2 - BO","051-555777");
Cliente C2=new Cliente("Caio","via Olmi 3 - BO","051-999777");
CC conto1=new CC(C1,1);
CC conto2=new CC(C1,2);
CC conto4=new CC(C2,4);
conto1.versa(2000.0);
conto2.versa(3000.0);
conto4.versa(4000.0);
conto1.preleva(4000.0);// fallisce supera saldo
conto1.preleva(2000.0);
conto2.preleva(3000.0);
conto4.preleva(3000.0);
conto1.mostraCli();
conto1.mostra();
conto2.mostraCli();
conto2.mostra();
conto4.mostraCli();
conto4.mostra();
CC conto3=new CC(C1,3); // fallisce sup.2 conti
}
}
L'output del programma:
Saldo insufficiente! OP Fallita.
Cliente :Tizio
Indirizzo :via Pioppa 2 - BO
Telefono: 051-555777
Conto n° 1
Operazioni svolte= 2
Saldo= 0.0
Cliente :Tizio
Indirizzo :via Pioppa 2 - BO
Conto n° 2
Operazioni svolte= 2
Saldo= 0.0
Telefono: 051-555777
appunti java
Cliente :Caio
Indirizzo :via Olmi 3 - BO
pag. 64
Telefono: 051-999777
Conto n° 4
Operazioni svolte= 2
Saldo= 1000.0
il cliente ha giù due conti. op Fallita!
7.3.
Le classi e l’ereditarietà
La relazione di Generalizzazione o Ereditarietà tra classi è una delle più
importanti tra quelle "supportate" dalla programmazione ad oggetti. Nell'esempio
del Cerchio si è segnalata la relazione associativa di Composizione e nella Conto
Corrente quella di Aggregazione, ora si vedrà la relazione associativa di
Generalizzazione o Ereditarietà tra classi. Questa caratteristica molto importante
consente di progettare classi complesse facendo ereditare a queste il codice scritto
precedentemente per altre classi genitrici “simili”. Questa proprietà è un punto di
forza di un linguaggio ad oggetti ma impone al “progettista” una visione ampia
delle necessità e dell’architettura che intende realizzare.
Procediamo con un esempio; l'esempio, pur avendo solo utilità didattica, mostra
alcuni punti di forza di questa metodologia.
Dopo la precedente progettazione della classe Razionale, si potrebbe pensare alla
seguente situazione problematica.
esempio 6 - "Si
desidera disporre, oltre ai Razionali, anche di
numeri Interi e se possibile eseguire operazioni, come la somma o
la sottrazione tra i due tipi diversi come avviene in matematica".
•
OOA
Crea_Numeri
Operazioni
Stampa_Risultati
• OOD
Dalla matematica si sa che i numeri Interi e Razionali si possono sommare e
sottrarre tra loro e si ottengono risultati interi o razionali corretti. Nel progetto
si potrebbe sfruttare la caratteristica dei numeri interi che "possono essere
pensati" come Razionali con denominatore uguale all'unità.
appunti java
pag. 65
In altre parole esiste un rapporto di generalizzazione tra interi e razionali che
risponde al criterio tipico dell'ereditarietà (Inheritance). Due classi stanno in
una relazione di ereditarietà una classe è Figlia (Subclass) e l'altra è Genitore
(Parent, Superclass), se la prima risponde al criterio di "essere un" .
In altre parole in Intero "E' UN" Razionale con denominatore uno e quindi
l'intero è figlio di razionale che diviene la classe genitrice. Questa relazione di
ereditarietà è rappresentata dal seguente schema nella figura 3:
Numero
Numero
Razionale
Naturale
Intero
Fig.2
Razionale
Intero
Razionale
Naturale
Intero
Fig.1
Fig.3
Nulla esclude di pensare la gerarchia in modo diverso e più esteso come nelle
figure 1 e 2. In esse si indica che esiste un genitore "astratto", la classe Numero,
di tutti gli insiemi numerici (Razionali, Interi e Naturali). In figura 2 si evidenzia
solo che sia i razionali che gli interi che i naturali sono Numeri ma tra di loro non
esiste una gerarchia. Nella figura 1 si mostra che un naturale è
contemporaneamente un Intero (positivo evidentemente), un Razionale (con
denominatore uno) ed è anche un Numero.
Si disegneranno ora Classi e Metodi del modello di Fig. 3.
- long N;
- long D;
Razionale
+ Razionale(long n, long d)
+ sum(Razionale B ) : Razionale;
+ sub(Razionale B ) : Razionale;
+ toString( ) : String;
- mcd(long a, long b) : long;
Intero
+ Intero(long n)
appunti java
pag. 66
La classe Intero è dotata del solo Costruttore infatti un intero è un Razionale e
quindi la classe eredita sia gli attributi che tutti i metodi definiti per la classe
genitrice. Non è necessario scrivere codice per ottenere le operazioni di somma e
sottrazione tra interi.
•
OOP
Ecco il codice completo delle due classi
La classe Razionale:
class Razionale
{
private long N, D;
public Razionale(long n, long d) {
long div=mcd(n,d);
n=n/div; d=d/div;
if (d<0) {n=-n; d=-d;}
N=n ;D=d;
}
public Razionale sum(Razionale B) {
long R=N*B.D+B.N*D;
long K=D*B.D;
Razionale Ris=new Razionale(R,K);
return Ris;
}
private static long mcd(long a, long b) {
long x=Math.abs(a);long y=Math.abs(b);
long ris=0;
if (y!=0) {
if (x!=0) {
long r=1;
while (r!=0) {
r= x % y;
x = y;
y = r;
}
ris=x;
}
else ris=y; // x=0, y diverso da zero
}
else {
// y=0 inaccettabile
System.out.println("Denominatore Nullo");
System.exit(0);
}
return ris;
} // Fine mcd
} // Fine Class Razionale
La classe Intero:
public class Intero extends Razionale {
public Intero(long a) {
super(a,1);
}
}
appunti java
pag. 67
Nella classe Razionale non si è sviluppato un metodo stampa() ma in sua vece si è
scritto un metodo toString() che restituisce la stringa che rappresenta il numero
Razionale. Tale metodo esiste predefinito in java nella classe Object e può essere
ridefinito (visto che Razionale e Figlio di Object) per disporre automaticamente
della possibilità di usare una variabile Razionale come parametro di stampa del
metodo System.out.println().
Per scrivere la classe Razionale con un Costruttore e i metodi sum(), sub() e
toString() si sono scritte una trentina di righe di codice (escludendo mcd() non
necessario per gli interi), ora con tre o quattro righe di codice si definiscono le
stesse operazioni tra interi. Il risparmio non è molto evidente ma questa
potenzialità deve essere proiettata su oggetti più complessi per vederne
l’economicità e la sicurezza di funzionamento. Se, con l'ereditarietà, si "appoggia"
una classe figlia "sulle spalle" di un codice sperimentato in precedenza si ha una
buona possibilità di evitare errori insidiosi, in genere proporzionali alle righe di
codice che si scrivono.
Ora si possono immediatamente costruire oggetti di tipo Intero e Razionale ed
eseguire le operazioni sum(), sub() e stampa con il seguente main.
public class es06_c07 {
public static void main(String arg[]) {
Razionale A=new Razionale(-8,-4);
Razionale B=new Razionale(1,-2);
Intero D=new Intero(3);
Intero E=new Intero(-3);
Razionale C=A.sum(B);
Razionale F=D.sum(E);
Razionale G=D.sub(E);
Razionale H=G.sum(B);
System.out.println("A="+A+" B="+B+" A+B="+C);
System.out.println("D="+D+" E="+E+" D+E="+F+" D-E="+G+" D-E+B="+H);
}
}
Eseguendo si ottiene:
A=2
D=3
B=-1/2 A+B=3/2
E=-3 D+E=0 D-E=6
7.4.
D-E+B=11/2
Sintesi e significato dei diagrammi introdotti
La simbologia utilizzata per descrivere Analisi (OOA) e Disegno (OOD) è un
estratto schematico preso a prestito dal linguaggio UML (Unified Modeling
Language) che viene largamente utilizzato in ambiente di progettazione ad
Oggetti. Anche se non si hanno pretese di rigore nell'uso di tali diagrammi si
manterrà d'ora in avanti tale schematizzazione tutte le volte che si presenterà il
progetto risolutivo di una situazione problematica.
Nella fase di OOA si è utilizzato il diagramma dei CASI D'USO che identifica, in
prima approssimazione, le funzionalità che il sistema da progettare deve fornire
all'utente. Le frecce, che a volte sono indicate, dall'utente verso i casi o viceversa
indicano la direzione dei dati input/output.
appunti java
pag. 68
Nella fase di OOD si è utilizzato in prevalenza il diagramma delle classi e delle
relazioni tra di esse. Tali diagrammi specificano con rigore una serie di
informazioni che saranno vincolanti e andranno a costituire i vincoli per il
programmatore del package. Si è visto che il manuale di specifica può essere
prodotto in questa fase o rimandato contemporaneamente alla codifica.
Oltre all'uso del diagramma delle classi si sono utilizzati i simboli che indicano se
un attributo o un metodo sono private (-), protected (#) o public (+)
<Nome>
<Attributi>
<Costruttori>
<Metodi>
Si e visto che le classi possono stare in relazione tra loro in diverse modalità
associative distinte in:
•
Composizione
Punto
Punto
Motore
1
3..*
0..4
Cerchio
Poligono
Velivolo
La composizione è una relazione "Tutto-Parti" tale che una classe "HA UN" o
risponde alla condizione di "AVERE COME SUA PARTE UN".
Nei casi disegnati
<la classe Cerchio ha sempre uno ed un solo Punto come centro>
<la classe Poligono ha sempre tre o più Punti che sono i vertici>
<la classe Velivolo ha sempre da 0 (aliante) a 4 Motori>
La classe di sinistra indicata dalla freccia è parte di un tutto (la classe di
destra).
appunti java
pag. 69
I numeri o gli intervalli (1), (3..*), (0..4) indicano che la classe "Tutto" può
possedere 0, 1 o più "Componenti".
La freccia indica la dipendenza dell'oggetto parte dal tutto e la navigabilità.
Per navigabilità si intende che si accede ai dati e ai servizi della classe "Parte" solo
a partire dalla classe "Tutto" che è responsabile del sistema. Questo fatto si
traduce, dal punto di vista del software, nel vincolo che in caso di "Cancellazione"
di un oggetto appartenente al "tutto" si cancella sempre anche la "parte".
•
Aggregazione
Quadrato
n
ContoCorrente
ContoCorrente
ContoCorrente
1
1
Stile
0..2
1
0..2
1
0..2
1
Cliente
n
Cerchio
Fig.1
Fig.2
Cliente
Fig.3
Cliente
Fig.4
L'aggregazione è una relazione che risponde ancora alla condizione "HA UN" come
per la composizione. Nel caso di Fig.1 si può dire che lo Stile (es. colore)
caratterizza un Cerchio o un Poligono. Si può notare che la classe Stile può essere
condivisa da più oggetti della classe Cerchio o Poligono non è quindi "La Parte di
un Tutto". Visto che uno stesso stile può essere condiviso da più oggetti non è più
vero che in caso di Cancellazione dell'oggetto che "lo possiede" si cancelli anche
questo.
In questo caso si nota che i numeri, presenti ad entrambi gli estremi del legame
che rappresenta la relazione, ci informano che un Cerchio può avere un solo Stile,
ma uno Stile può essere associato a (n) Cerchi o Poligoni diversi.
La freccia indica ancora dipendenza e navigabilità come per la composizione.
Le Fig.2, 3 e 4 mostrano altri modo di indicare una relazione. La due è ancora
una composizione, la 3 indica espressamente la doppia navigabilità e la 4 indica
genericamente la molteplicità della relazione tra le due classi senza impegnarsi
nel definire la navigabilità o il tipo Composizione o Aggregazione.
•
generalizzazione o ereditarietà;
Numero
Razionale
Figura
Intero
Cerchio
Poligono
Naturale
appunti java
pag. 70
La relazione di generalizzazione ci informa che una classe "E' UN" caso
particolare di una classe più generale. In particolare la Classe Cerchio è una
Figura così come lo è la classe Poligono. Mentre abbiamo visto che un Naturale
può essere "pensato" come Intero positivo e questo come un Razionale con
denominatore 1.
7.5.
Progettazione di una gerarchia di classi
Dagli esempi sviluppati può non essere del tutto chiaro come ci si debba
comportare per progettare correttamente una serie di classi usando il metodo
dell’ereditarietà. Infatti qualche “matematico” potrebbe chiedersi perché gli Interi
siano figli dei Razionali e non viceversa visto che nello studio degli insiemi
numerici si inizia dai numeri Naturali e per estensione si ottengono gli Interi e
successivamente i Razionali e così via.
La progettazione ereditaria procede di norma dal generale al particolare o se
preferite del generico allo specifico nel senso che in una gerarchia di oggetti “il
genitore per eccellenza”, il capostipite da cui tutto discende deve essere una
classe “generica” o “astratta” ovvero tale da contenere tutte le proprietà comuni a
tutti i figli, i discendenti devono mostrare “particolarità aggiuntive-diverse o
restrittive-mancanti” rispetto a quelle valide per il capostipite. In questo caso
l’insieme dei Razionali (è più “ampio”) contiene quello degli Interi almeno rispetto
alla definizione (non rigorosa ma operativamente utile) adottata. Si è proceduto
per restrizione. Procedendo ancora su questa strada si scoprirebbe che per gli
interi è definibile un'operazione di divisione diversa da quella tra razionali. Mentre
nei Razionali il quoziente é sempre eseguibile se il divisore è diverso da zero, per
gli interi il quoziente non può restituire sempre un intero, infatti nel caso in cui il
dividendo non sia esattamente divisibile, restituirebbe un razionale. Se si fossero
progettati prima gli interi e poi i razionali le modalità di implementazione
sarebbero state diverse.
La scelta se progettare per “estensione-specializazione” o per “restrizione” deve
fare i conti con gli oggetti specifici e con i metodi concreti che si intendono
sviluppare.
Come esempio di progettazione per specializzazione si potrebbe adottare la
seguente
Figura_con_Centro
Punto
Quadrato
Cubo
Cerchio
Sfera
Rombo
La gerarchia disegnata potrebbe iniziare anche dalla classe Punto eliminando la
classe Figura_con_Centro, infatti quest’ultima è tipicamente una classe
“Astratta”; non rappresenta nessun oggetto concreto disegnabile ma ha la pretesa
di rappresentarli tutti genericamente. La classe Punto è la prima classe
“Concreta” nel senso che potrebbe essere "istanziata" e dotata di Attributi e
Metodi specifici.
appunti java
pag. 71
Se nella figura ci si sposta da sinistra a destra si incontrano classi che
“estendono” il Punto nel senso che pur mantenendo un attributo in comune, il
Centro, ne hanno altri che li caratterizzano per estensione. Il Quadrato oltre al
Centro è dotato di un Lato, il Cerchio di un Raggio, il Rombo di due Diagonali che
lo caratterizzano. Il Cubo eredita dal quadrato Centro e Lato ma si può
immaginare per estensione che sia dotato dell’attributo Volume che il quadrato
non ha, così la Sfera.
7.6.
Overloading e Overriding dei metodi
Overloading si traduce nella parola Sovraccarico e si tratta della possibilità,
all'interno di una classe, di definire dei metodi (o dei costruttori) che hanno lo
stesso nome (identificatore) ma signature (intestazione) diversa.
Visto che la Signature caratterizza l'Overloading si precisa ulteriormente tale
concetto. La Signature o Intestazione di un Metodo o di un Costruttore è
definita dalla sua forma e si compone di:
<identificatore> (<parametri>); dove <identificatore> é il nome del metodo, e
<parametri> contiene i dati trasmessi.
Due signature con lo stesso <identificatore> sono distinte se e solo se hanno o
un numero diverso di parametri o hanno parametri di tipo diverso.
public Intero opera(int, long);
public Double opera(int, long);
Sono due metodi con IDENTICA signature perché il tipo restituito non distingue i
metodi.
public Intero opera(int, long);
public Intero opera(long, long);
Sono due metodi con signature DIVERSA.
Questa possibilità consente di difinire più Costruttori (che hanno tutti il nome
della classe) o diversi Metodi, con lo stesso nome, che hanno funzionalità diverse.
Utilizzando le classi di java come StringBuffer o Integer si è fatto implicitamente
uso di costruttori diversi dello stesso oggetto, questo presuppone che nella classe
quei costruttori siano stati definiti in Overloading.
Se si consulta la documentazione di Integer si scopre che esistono due costruttori
con signature diversa:
Integer(int n);
(costruisce l'oggetto Integer a partire da un int)
Integer(String ns).
(costruisce l'oggetto Integer a partire da una String)
Per la classe StringBuffer ne esistono tre:
StringBuffer();
(di default) crea un buffer vuoto per ospitare caratteri;
StringBuffer(int n);
crea un buffer di dimensione n vuoto;
StringBuffer(String S); crea il buffer con i caratteri della stringa.
appunti java
pag. 72
Esistono metodi di StringBuffer molto utilizzati che hanno definizioni in
Overloading come il metodo append() e substring().
StringBuffer append(char ch)
StringBuffer append(StringBuffer sb)
StringBuffer append(String s)
String substring(int start)
String substring(int start, int end)
Per mostrare un ulteriore esempio di Overloading potremmo prendere in esame la
classe Razionale e il suo costruttore Razionale(long n, long d) e sovraccaricarlo
definendo un nuovo costruttore che riceva una Stringa del tipo "1/2" e costruisca
il razionale o ancora un costruttore di default che costruisca il razionale Nullo
0/1. Ecco quindi i tre costruttori in sovraccarico con le diverse signature:
Razionale()
Razionale(long n, long d);
Razionale(String s);
Proseguendo con l'esempio Razionale si potrebbe sovraccaricare il metodo somma
con un metodo che addizioni non due ma tre razionali contemporaneamente si
otterrà:
Razionale sum(Razionale R);
Razionale sum(Razionale R, Razionale S);
Overriding si traduce nella parola Ridefinizione e si riferisce alla possibilità di
ridefinire un metodo della classe genitrice in una sua sottoclasse mantenendo lo
stesso nome e la stessa signature.
In sostanza per overriding dei metodi si intende la possibilità di definire
(ridefinire) in una Sottoclasse un metodo con la stessa signature presente nella
Superclasse al fine di far eseguire operazioni distinte a seconda che l'oggetto
invocante sia il Genitore o il Figlio.
Questa possibilità è realizzabile perché un linguaggio di programmazione ad
oggetti opera sempre "legando dinamicamente", durante l'esecuzione, l'oggetto
invocante al suo metodo. La proprietà
rende indirettamente più semplice
ricordare a memoria i metodi che in un linguaggio ad oggetti si possono
moltiplicare indefinitamente con la progettazione di nuove classi e sottoclassi.
esempio 7 - "Si desidera disporre di una gerarchia di classi che
realizzi, crei e disegni, sequenze di segmenti collegati vale a
dire spezzate e poligoni."
• OOD
La gerarchia di classi disegnata discende dalla presa d'atto che un Poligono "E'
UNA" Spezzata chiusa. Si nota dal diagramma che il metodo disegna(), presente in
entrambe le classi, ha la stessa signature, ma il disegno di un Poligono deve
estendere quello di Spezzata tracciando l'ultimo segmento di chiusura. Questa è
la tipica situazione di Overriding di un metodo anzi in questo caso non è
appunti java
pag. 73
necessario riscrivere completamente il metodo della sottoclasse ma è sufficiente
aggiungere al disegno di spezza la linea di chiusura.
Spezzata
Poligono
# int X[ ], Y[ ];
+ Spezzata( int a[ ], int b[ ]);
+ disegna(Jframe F) : void;
+ getLun( ) : int;
•
+ Poligono( int a[ ], int b[ ]);
+ disegna(Jframe F) : void;
OOP
Nel codice che segue è stata introdotta anche una classe myframe che funge da
contenitore per il disegno. Tale classe non verrà commentata in quanto nel
contesto della discussione non ha rilievo.
Ecco il codice della gerarchia con l'operazione disegna() ridefinita, le parti da
notare sono in grassetto:
import java.awt.*;
import javax.swing.*;
public class Spezzata
{
protected int X[], Y[];
protected Graphics g
public Spezzata(int a[], int b[]){
X=a; Y=b;
}
public void disegna(JFrame F) {
g=F.getGraphics();
g.setColor(Color.black);
for (int i=0; i<getLun()-1; i++)
g.drawLine(X[i],Y[i],X[i+1],Y[i+1]);
F.update(g);
}
public int getLun() { // determina la dimensione dell'array
return X.length;
}
}
import java.awt.*;
import javax.swing.*;
public class Poligono extends Spezzata {
public Poligono(int a[], int b[]){
super(a,b);
}
public void disegna(JFrame F) {
super.disegna(F); // richiama disegna() di Spezzata
g.setColor(Color.red);
g.drawLine(X[0],Y[0],X[getLun()-1],Y[getLun()-1]); // chiude
F.update(g);
}
}
appunti java
pag. 74
Il main() costruisce DUE poligonali di vertici x[ ] y[ ] e x[ ] z[ ] la seconda si
distingue perché trasla le ordinate y dei punti in basso di 100 pixel rispetto alla
prima. Quindi si creano i due oggetti che sono assegnati (istanziati) come
Spezzate e poi li disegna.
public class es07_c07 {
public static void main(String ar[]){
myframe F=new myframe();
F.setVisible(true);
int x[]={10,30,100,150};
int y[]={50,100,150,70};
int z[]={150,200,250,170};
Spezzata Q=new Spezzata(x, y); // Notare che sono entrambi
Spezzata P=new Poligono(x, z); // Spezzate.
Q.disegna(F);
P.disegna(F);
}
}
A titolo di pura curiosità si presenta il codice che genera la JFrame che conterrà il
disegno;
import javax.swing.*;
import java.awt.*;
public class myframe extends JFrame{
public myframe(){
setSize(160,260);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void paint(Graphics g) {
}
}
l'output è il seguente:
La ridefinizione di un metodo nell'ambito dell’ereditarietà della programmazione
ad oggetti ha consentito di far “comprendere” al compilatore che la spezzata Q era
un poligono e ha applicato il metodo di disegno corretto.
appunti java
7.7.
pag. 75
La gerarchia di classi e i packages di Java
Dopo i primi passi sulla progettazione e nella sintassi del linguaggio è necessaria
una esplorazione delle classi presenti nel linguaggio. Si tratta di chiarire quali
entità di java sono Oggetti e quali non lo sono.
Nell’introduzione si è fatto uso di Tipi di Dati Predefiniti ma anche di Oggetti.
• I tipi int, float, boolean, double sono variabili di tipo Statico (non sono
allocati dinamicamente nella memoria HEAP) mentre il tipo array viene
allocato dinamicamente in memoria con una operazione new.
• I tipi String, StringBuffer, Float, Intger, Frame sono Classi presenti in Java
e progettate dalla SUN Microsystem che distribuisce il linguaggio.
• infine Razionale, Intero, CC, Spezzata e Poligono sono Classi progettate
successivamente.
Il linguaggio Java è stato progettato come linguaggio ad oggetti ma utilizza anche
variabili numeriche o booleane statiche. La ragione della presenza di variabili
statiche di tipo numerico o logico è quella di rendere più efficiente l’esecuzione di
programmi di calcolo numerico.
Per completare la trattazione dei tipi di dati e degli oggetti è opportuno precisare
che la Gerarchia di classi di Java consente sempre di trasformare un tipi di dato
numerico statico nel corrispondente Oggetto dinamico e viceversa secondo lo
schema seguente:
tipi
statici
Dim.
fisica
in bit
byte
short
char
int
long
float
double
boolean
8
16
16
32
64
32
64
8
Oggetti
dinamici
(involucro
per i tipi
statici)
Byte
Short
Character
Integer
Long
Float
Double
Boolean
Costruttore degli "involucri" dei Metodi di Ritrasformazione
degli
Oggetti
tipi statici (Oggetti)
numerici nei tipi statici
restituisce
corrispondenti
Byte
Short
Character
Integer
Long
Float
Double
Boolean
Byte(byte x)
Short(short s)
Character(char c)
Integer(int i)
Long(long l)
Float(float f)
Double(double d)
Boolean(boolean t)
char ch = C.charValue();
int
i = I.intValue();
long l = L.longValue();
float f = F.floatValue();
double d = D.doubleValue();
Per concludere ricordiamo che in Java le classi sono sempre raggruppate in
packages. Un Package riunisce logicamente un gruppo di classi “affini”. I
packages più importanti che si sono utilizzati fino ad ora o che si cominceranno a
studiare sono i seguenti:
java.lang (contiene gli elementi fondanti, in particolare la classe Object che è la
radice di ogni oggetto di java, ma anche String, Integer, Float ecc.
trattati in precedenza)
java.awt (contiene tutte le classi Grafiche, Frame, Button, Textfield, TextArea,
Menu ecc.)
java.awt.event (che consente la gestione degli eventi mouse, tastiera ecc.);
appunti java
pag. 76
javax.swing (analoga ad awt, contiene JFrame, JButton, JTextField, JTextArea,
JMenu ecc. sarà quasi sempre utilizzata in sostituzione della awt)
java.io
(contiene tutte le classi che lavorano su flussi di input output e sui
file memorizzati su dispositivi esterni)
java.applet (che consente la costruzione delle Applet).
java.util (contiene le classi che definiscono Strutture Dati dinamiche molto
utili, le classi Vector, Stack, LinkedList, Properties, Date ecc.)
7.E - Esercizi
•
Esercizi di specifica e codifica di un metodo aggiunto ad una classe
disegnata in precedenza.
7.1 Facendo riferimento al progetto della classe Razionale del paragrafo 7.2
“Accrescere il disegno e la codifica della classe razionale con i seguenti metodi:
differenza sub(), prodotto mlt(), quoziente fra(),”. Richieste:
a) scrivere con Word il documento di specifica dei singoli metodi facendo attenzione ai
vincoli o eccezioni delle operazioni (vedi esempi paragrafo 7.2)
b) provare ad utilizzare javadoc per ottenere la documentazione on line della Classe e
dei suoi metodi.
c) codificare la classe Razionale con i metodi disegnati;
d) verificare con un semplice main() il funzionamento dei metodi in articolare delle
eccezioni previste.
7.2 Facendo riferimento al progetto della classe Razionale “Completare il disegno e
la codifica della classe razionale con i seguenti metodi: approssimazione di un
razionale ad un decimale app(), estrazione del numeratore num(), estrazione del
denominatore den(). ”. Richieste:
a) scrivere il documento di specifica dei singoli metodi facendo attenzione ai vincoli o
eccezioni delle operazioni (vedi esempi paragrafo 7.2)
b) codificare la classe Razionale con i metodi disegnati;
c) verificare con un semplice main() il funzionamento dei metodi.
7.3 Facendo riferimento al progetto della classe Razionale “Sapreste disegnare
una operazione di elevazione a potenza di un razionale con un esponente
appartenente a N0 ? Quale limite porre all'esponente n ?"”. Richieste:
a) scrivere la specifica del metodo facendo attenzione ai vincoli o eccezioni
dell'operazione.
b) codificare il metodo disegnato;
c) verificare con un semplice main() il funzionamento.
7.4 Facendo riferimento al progetto della classe Razionale “Sapreste migliorare il
disegno dell'operazione di elevazione a potenza, dell'esercizio 7.3, utilizzando un
appartenente a Z ? Quale limite porre all'esponente ?"”. Richieste:
a) scrivere la specifica del metodo facendo attenzione ai vincoli o eccezioni
dell'operazione.
b) codificare il metodo disegnato;
c) verificare con un semplice main() il funzionamento.
appunti java
pag. 77
7.5 Facendo riferimento al progetto della classe Conto Corrente dell'esempio 5 del
testo “Sapreste codificare il disegno conseguente all'analisi riportata a pag, 61 nella
quale Cliente è in composizione con CC ?" Richieste:
a) scrivere la specifica dei nuovi metodi rispettando i diagrammi delle classi.
b) codificare classi i metodi;
c) verificare che il main() sotto assegnato produca l'output corretto.
Si può notare che main() invoca sempre i metodi di CC e di Cliente a partire da un
oggetto Cliente. La navigabilità e da Cliente a CC.
public class eser_05_c07 {
public static void main(String ar[]) {
Cliente cli1, cli2;
cli1=new Cliente("Tizio", "Via Villa 7 - BO","777000");
cli2=new Cliente("Caio", "Via Casetta 33 - BO","333000");
cli1.creaConto(1);
cli1.creaConto(2);
cli1.creaConto(3);
// fallisce ha già due conti
cli1.contoUno().versa(1000);
cli1.contoUno().versa(2000);
cli1.contoDue().versa(7000);
cli1.contoUno().preleva(2000);
cli1.contoDue().preleva(8000);// Saldo insufficiente
cli1.mostra();
cli1.mostraCC();
cli2.mostra();
cli2.mostraCC();
cli2.contoUno().preleva(6000); // fallisce non ha conto
}
}
Output:
il Cliente ha già DUE conti. Op. Fallita.
Saldo insufficiente! OP Fallita.
Cliente :Tizio
Indirizzo :Via Villa 7 - BO
Conto n° 1
Operazioni svolte= 3
Saldo= 1000.0
Telefono: 777000
Conto n° 2
Operazioni svolte= 1
Saldo= 7000.0
Cliente :Caio
Indirizzo :Via Casetta 33 - BO Telefono: 333000
Il cliente non ha Conti aperti.
Conto inesistente!!
7.6 Facendo riferimento al progetto della classe Conto Corrente dell'esempio 5 del
testo “Sapreste codificare il disegno conseguente all'analisi riportata a pag, 61 nella
quale Cliente in relazione di doppia navigabilità con CC ?" Richieste:
a) scrivere la specifica dei nuovi metodi rispettando i diagrammi delle classi.
b) codificare classi i metodi;
c) verificare che il main() sotto assegnato produca l'output corretto.
appunti java
pag. 78
Si può notare che main() invoca i metodi di CC e di Cliente sia a partire da un oggetto
CC che da un oggetto Cliente. La navigabilità è in entrambi i sensi.
public class eser_06_c07 {
public static void main(String ar[]) {
Cliente1 cli1, cli2;
cli1=new Cliente1("Tizio", "Via Villa 7 - BO","777000");
cli2=new Cliente1("Caio", "Via Casetta 33 - BO","333000");
// navigabilità da Cliente a CC
cli1.creaConto(1);
cli1.creaConto(2);
cli1.contoUno().versa(1000);
cli1.contoUno().versa(2000);
cli1.contoDue().versa(7000);
cli1.contoUno().preleva(2000);
cli1.contoUno().preleva(2000); // fallisce Saldo insuff.
cli1.contoDue().preleva(6000);
cli1.mostra();
cli1.mostraCC();
cli2.mostra();
cli2.mostraCC();
// Navigabilità da CC a Cliente
CC1 cc=new CC1(cli2,3);
cc.versa(11000);
cc.preleva(9999);
cc.mostraCli();
// cli 2 !!
cc.mostra();
cc=cli1.contoDue();
cc.versa(9999);
cc.mostraCli();
// cli 1 !!
cc.mostra();
cli1.creaConto(3);
// fallisce ha già due conti
}
}
output:
Saldo insufficiente! OP Fallita.
Cliente :Tizio
Indirizzo :Via Villa 7 - BO
Conto n° 1
Operazioni svolte= 3
Saldo= 1000.0
Telefono: 777000
Conto n° 2
Operazioni svolte= 2
Saldo= 1000.0
Cliente :Caio
Indirizzo :Via Casetta 33 - BO Telefono: 333000
Il cliente non ha Conti aperti :
Cliente :Caio
Indirizzo :Via Casetta 33 - BO
Conto n° 3
Operazioni svolte= 2
Saldo= 1001.0
Cliente :Tizio
Indirizzo :Via Villa 7 - BO
Conto n° 2
Operazioni svolte= 3
Saldo= 10999.0
Telefono: 333000
Telefono: 777000
Ha superato i 2 conti! Op. Fallita!
appunti java
pag. 79
7.7 Utilizzando la classe Razionale “Realizzare un programma main() che richieda
all'utente un numero naturale n e stampi gli n termini della serie che genera π. Il
programma deve stampare tutte le somme parziali della serie sia in forma razionale
che con approssimazione reale"
π
La successione che converge (molto lentamente) a
è la seguente:
4
π
1 1 1
1
= 1 − + − + ... + ( −1) n +1
+ .... o anche quella che converge a π = 4(…)
4
3 5 7
2n − 1
la stampa richiesta con n = 6 sarà:
n
1
2
3
4
5
6
Frazione
4
8/3
52/15
304/105
1052/315
10312/3465
Approssimazione
4
2,6666667
3,4666667
2,8957382
3,3396826
2,976046
Richiesta:
a) codificare e verificare il funzionamento del programma facendo uso della classe
Razionale;
•
Esercizi di specifica e codifica di una gerarchia di classi disegnata
parzialmente in precedenza.
7.8 Utilizzando la gerarchia numerica di fig. 1 disegnata a pagina 65 ma
escludendone la classe Numero “Specificare e codificare l'intera gerarchia
Razionale, Intero, Naturale, con costruttori e operazioni di somma, sottrazione,
prodotto e stampa. Ampliarla con operazioni di divisione tra interi e naturali:quali la
fra() che deve restituire ancora un razionale la quo() che restituisce un decimane
approssimato e div() che esegue la divisione intera"
a) Disegnare le tre Classi per intero collocando i metodi correttamente.
b) scrivere la specifica dei nuovi metodi rispettando i diagrammi delle classi.
c) codificare classi i metodi;
d) verificare con un semplice main() il funzionamento.
appunti java
pag. 80
7.9 Utilizzando la gerarchia del precedente esercizio “ Completare la sottoclasse
intero con i metodi modulo mod(), valore assoluto abs(), massimo comun divisore
mcd()" Richieste:
a) scrivere le specifiche dei metodi facendo attenzione ai vincoli o eccezioni delle
operazioni.
b) codificare i metodi disegnati;
c) verificare con un semplice main() il funzionamento.
7.10 Utilizzando la gerarchia di fig. 2 disegnata a pagina 65 “Specificare e
codificare l'intera gerarchia Numero, Razionale, Intero, Naturale, con costruttori e
operazioni di somma, sottrazione, prodotto, quoziente e stampa "
a)
b)
c)
d)
Disegnare le quattro Classi interamente collocando i metodi correttamente.
scrivere la specifica dei nuovi metodi rispettando i diagrammi delle classi.
codificare classi i metodi;
verificare con un semplice main() il funzionamento.
•
Esercizi di progettazione completa a partire da una situazione
problematica data.
7.11 “Realizzare un programma che consenta di immettere e risolvere equazioni di
secondo grado." Richieste:
a)
b)
c)
d)
disegnare i casi d'USO e se necessario descriverli brevemente;
disegnare la/le classi necessarie con le eventuali relazioni tra loro.
scrivere la specifica dei nuovi metodi rispettando i diagrammi delle classi.
codificare classi i metodi;
e) verificare con un semplice main() il funzionamento.
7.12 “Realizzare un programma che consenta di immettere e risolvere sistemi di
primo grado in due equazioni e due incognite. Si richiede che sia possibile stabilire
prima della risoluzione se il sistema è impossibile o indeterminato" Richieste:
a)
b)
c)
d)
e)
disegnare i casi d'USO e se necessario descriverli brevemente;
disegnare la/le classi necessarie con le eventuali relazioni tra loro.
scrivere la specifica dei nuovi metodi rispettando i diagrammi delle classi.
codificare classi i metodi;
verificare con un semplice main() il funzionamento.
7.13 Prendendo spunto dalla gerarchia Spezzata-Poligono dell'esempio 7 del testo
"Realizzare un programma che consenta di immettere e disegnare Quadrilateri,
Rettangoli e Quadrati persati come Gerarchia. Deve essere possibile costruire e
disegnare gli oggetti." Richieste:
a)
b)
c)
d)
disegnare i casi d'USO e se necessario descriverli brevemente;
disegnare la/le classi necessarie con le eventuali relazioni tra loro.
codificare classi i metodi;
verificare con un semplice main() il funzionamento.
appunti java
pag. 81
7.14 “Realizzare un programma che consenta di gestire una raccolta di
Videocassette che contengono due generi distinti VideoClip e Film. I VideoClip si
caratterizzano per Titolo, Regista, Cantante, Disponibilità mentre i film per Titolo
Regista, Durata, Disponibilità. Deve essere possibile Affittarle a Clienti della
Videoteca, costruire-memorizzare nuovi Titoli, Verificarne la disponibilità mostrando i
dati che si riferiscono a un Titolo. " ." Richieste:
a) disegnare i casi d'USO e se necessario descriverli brevemente;
b) disegnare la/le classi necessarie con le eventuali relazioni tra loro.
c) codificare classi i metodi;
d) verificare con un semplice main() il funzionamento.
7.15 Facendo riferimento all'esercizio 7.12 “Realizzare un programma che consenta
di immettere e risolvere sistemi di primo grado in due equazioni e due incognite con il
vincolo che i coefficienti e le soluzioni siano Numeri Razionali." Richieste:
Facendo uso della classe Razionale progettata in precedenza
a) disegnare i casi d'USO e se necessario descriverli brevemente;
b) disegnare la/le classi necessarie con le eventuali relazioni tra loro.
c) codificare classi i metodi;
d) verificare con un semplice main() il funzionamento.