appunti java – Capitolo 7 pag. 1 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 con il solo main() o con main() e metodi statici; applet; Classi “contenitore” di metodi statici o 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 – Capitolo 7 pag. 2 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 – Capitolo 7 7.2. pag. 3 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 – Capitolo 7 pag. 4 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 – Capitolo 7 pag. 5 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 – Capitolo 7 pag. 6 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: public class cap07_es01 { public static void main(String arg[]) { appunti java – Capitolo 7 pag. 7 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 – Capitolo 7 pag. 8 Lo schema di disegno della classe è il seguente: Razionale - long N; - long D; + 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 Identificatore int Num; int Den; Intestazione: Razionale private 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. Razionale(long n, long d) 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 – Capitolo 7 pag. 9 /** * 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ù Tools di GEL l’opzione <Java> e successivamente <Generate Javadoc>. Si apre una prima finestra di dialogo che consente di impostare i criteri di costruzione della manualistica e una seconda finestra che, con la pressione del bottone execute, avvia il processo di generazione automatica della documentazione attraverso la creazione di una cartella Docs all’interno del path che contiene il codice sorgente. Al termine, se non vengono segnalati errori, si apre la cartella e si avvia <index.html> visualizzando la documentazione di Razionale. appunti java – Capitolo 7 pag. 10 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 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 cap07_es02 { 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) appunti java – Capitolo 7 pag. 11 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. 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 cap07_es03 { 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 – Capitolo 7 pag. 12 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 è 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: Posiziona 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 – Capitolo 7 pag. 13 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 cap07_es04 { 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 – Capitolo 7 pag. 14 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 – Capitolo 7 pag. 15 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 – Capitolo 7 pag. 16 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("Operazioni 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 – Capitolo 7 pag. 17 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 cap07_es05 { 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 – Capitolo 7 Cliente :Caio Indirizzo :via Olmi 3 - BO pag. 18 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 – Capitolo 7 pag. 19 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 – Capitolo 7 pag. 20 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; } public Razionale sub(Razionale r) { long nris, dris; nris = N*r.D-D*r.N; dris = D*r.D; return new Razionale(nris,dris); } 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 public String toString() { if (D==1) return ""+N; else return ""+N+"/"+D; } appunti java – Capitolo 7 pag. 21 } // Fine Class Razionale La classe Intero: public class Intero extends Razionale { public Intero(long a) { super(a,1); } } 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 è 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 cap07_es06 { 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 appunti java – Capitolo 7 pag. 22 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. 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 appunti java – Capitolo 7 pag. 23 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). 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 0..2 Stile 1 0..2 1 0..2 1 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 modi di indicare una relazione. La 2 è 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. appunti java – Capitolo 7 pag. 24 generalizzazione o ereditarietà; Numero Figura Razionale Cerchio Poligono Intero Naturale 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-specializzazione” o per “restrizione”, deve fare i conti con gli oggetti specifici e con i metodi concreti che si intendono sviluppare. appunti java – Capitolo 7 pag. 25 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. 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. appunti java – Capitolo 7 pag. 26 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. 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. appunti java – Capitolo 7 pag. 27 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 è necessario riscrivere completamente il metodo della sottoclasse, ma è sufficiente aggiungere al disegno di spezzata 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 appunti java – Capitolo 7 pag. 28 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); } } 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 cap07_es07 { 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) { } } appunti java – Capitolo 7 pag. 29 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 – Capitolo 7 7.7. pag. 30 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 Ritrasformatipi statici (Oggetti) zione degli 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 – Capitolo 7 pag. 31 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 – Capitolo 7 pag. 32 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 – Capitolo 7 pag. 33 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 – Capitolo 7 pag. 34 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 – Capitolo 7 pag. 35 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 – Capitolo 7 pag. 36 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.