Esercizi
- Home
- Esercizi Capitolo 5
Capitolo 5
Incapsulamento e visibilità
È ora di entrare pian piano nella giusta mentalità object oriented. Alcune delle soluzioni degli
esercizi che troverete di seguito, evidenzieranno le tipiche difficoltà che si incontrano quando si
programma, e come evitarle.
Di seguito trovate gli esercizi del capitolo.
Per
ogni esercizio, cliccando sulla traccia potete vedere la relativa soluzione.
Gli esercizi caratterizzati dall'icona sono
considerati i più complessi relativamente agli argomenti trattati.
Se preferite lavorare offline, è possibile scaricare tutti gli esercizi e le relative
soluzioni in formato PDF nella sezione download.
-
Esercizio 5.a) Object Orientation in generale (teoria), Vero o Falso:
1. L'Object Orientation esiste solo da pochi anni.
2. Java è un linguaggio object oriented non puro, SmallTalk è un linguaggio object oriented puro.
3. Tutti i linguaggi orientati agli oggetti supportano allo stesso modo i paradigmi object oriented. Si può dire che un linguaggio è object oriented se supporta incapsulamento, ereditarietà e polimorfismo; infatti altri paradigmi come l'astrazione e il riuso appartengono anche alla filosofia funzionale.
4. Applicare l'astrazione significa concentrarsi solo sulle caratteristiche importanti dell'entità da astrarre.
5. La realtà che ci circonda è fonte d'ispirazione per la filosofia object oriented.
6. L'incapsulamento ci aiuta ad interagire con gli oggetti, l'astrazione ci aiuta ad interagire con le classi.
7. Il riuso è favorito dall'implementazione degli altri paradigmi object oriented.
8. L'ereditarietà permette al programmatore di gestire in maniera collettiva più classi.
9. L'incapsulamento divide gli oggetti in due parti separate: l'interfaccia pubblica e l'implementazione interna.
10. Per l'utilizzo dell'oggetto basta conoscere l'implementazione interna, non è necessario conoscere l'interfaccia pubblica.
Soluzione
1. Falso, esiste dagli anni '60.
2. Vero.
3. Falso, ogni linguaggio fornisce supporto ai vari paradigmi in maniera diversa.
4. Vero.
5. Vero.
6. Vero.
7. Vero.
8. Vero.
9. Vero.
10. Falso, bisogna conoscere l'interfaccia pubblica e non l'implementazione interna. -
Esercizio 5.b)
Incapsulare e completare le seguenti classi:
public class Pilota { public String nome; public Pilota(String nome) { // impostare il nome } } public class Auto { public String scuderia; public Pilota pilota; public Auto(String scuderia, Pilota pilota) { // impostare scuderia e pilota } public String dammiDettagli() { // restituire una stringa descrittiva dell'oggetto } }
Tenere presente che le classi
Auto
ePilota
devono poi essere utilizzate dalle seguenti classi:
public class TestGara { public static void main(String args[]) { Gara imola = new Gara("GP di Imola"); imola.corriGara(); String risultato = imola.getRisultato(); System.out.println(risultato); } } public class Gara { private String nome; private String risultato; private Auto griglia []; public Gara(String nome) { setNome(nome); setRisultato("Corsa non terminata"); creaGrigliaDiPartenza(); } public void creaGrigliaDiPartenza() { Pilota uno = new Pilota("Pippo"); Pilota due = new Pilota("Pluto"); Pilota tre = new Pilota("Topolino"); Pilota quattro = new Pilota("Paperino"); Auto autoNumeroUno = new Auto("Ferrari", uno); Auto autoNumeroDue = new Auto("Renault", due); Auto autoNumeroTre = new Auto("BMW", tre); Auto autoNumeroQuattro = new Auto("Mercedes", quattro); griglia = new Auto[4]; griglia[0] = autoNumeroUno; griglia[1] = autoNumeroDue; griglia[2] = autoNumeroTre; griglia[3] = autoNumeroQuattro; } public void corriGara() { int numeroVincente = (int)(Math.random() * 4); Auto vincitore = griglia[numeroVincente]; String risultato = vincitore.dammiDettagli(); setRisultato(risultato); } public void setRisultato(String vincitore) { this.risultato = "Il vincitore di " + this.getNome() + ": " + vincitore; } public String getRisultato() { return risultato; } public void setNome(String nome) { this.nome = nome; } public String getNome() { return nome; } }
La classe
TestGara
contiene il metodomain
e quindi determina il flusso di esecuzione dell'applicazione. È molto leggibile: si istanzia un oggettogara
e lo si chiama "GP di Imola", si fa correre la corsa, si richiede il risultato e lo si stampa a video.
La classeGara
invece contiene pochi e semplici metodi e tre variabili d'istanza:nome
(il nome della gara),risultato
(una stringa che contiene il nome del vincitore della gara se è stata corsa) egriglia
(un array di oggettiAuto
che partecipano alla gara).
Il costruttore prende in input una stringa con il nome della gara che viene opportunamente impostato. Inoltre il valore della stringarisultato
è impostata a "Corsa non terminata". Infine è chiamato il metodocreaGrigliaDiPartenza
.
Il metodocreaGrigliaDiPartenza
istanzia quattro oggettiPilota
assegnando loro dei nomi. Poi istanzia quattro oggettiAuto
assegnando loro i nomi delle scuderie ed i relativi piloti. Infine istanzia ed inizializza l'arraygriglia
con le auto appena create. Una gara, dopo essere stata istanziata, è pronta per essere corsa.
Il metodocorriGara
contiene codice che va analizzato con più attenzione. Nella prima riga, infatti, viene chiamato il metodorandom
della classeMath
(appartenente al packagejava.lang
che viene importato automaticamente). La classeMath
astrae il concetto di matematica e sarà descritta più avanti in questo libro. Essa contiene metodi che astraggono classiche funzioni matematiche, come la radice quadrata o il logaritmo. Tra questi metodi utilizziamo il metodorandom
che restituisce un numero generato in maniera casuale di tipodouble
, compreso tra 0 ed 0,9999999... (ovvero il numerodouble
immediatamente più piccolo di 1). Nell'esercizio abbiamo moltiplicato per 4 questo numero, ottenendo un numerodouble
casuale compreso tra 0 e 3,99999999... Questo è poi convertito ad intero, quindi vengono troncate tutte le cifre decimali. Abbiamo quindi ottenuto che la variabilenumeroVincente
immagazzini al runtime un numero generato casualmente, compreso tra 0 e 3, ovvero i possibili indici dell'arraygriglia
.
Il metodocorriGara
genera quindi un numero casuale tra 0 e 3. Lo utilizza per individuare l'oggettoAuto
dell'array griglia che vince la gara, per poi impostare il risultato tramite il metododammiDettagli
dell'oggettoAuto
(che scriverà il lettore). Tutti gli altri metodi della classe sono di tipo accessor e mutator.
Soluzione
Il listato potrebbe essere simile al seguente:
public class Pilota { private String nome; public Pilota(String nome) { setNome(nome); } public void setNome(String nome) { this.nome = nome; } public String getNome() { return nome; } } public class Auto { private String scuderia; private Pilota pilota; public Auto(String scuderia, Pilota pilota) { setScuderia(scuderia); setPilota(pilota); } public void setScuderia(String scuderia) { this.scuderia = scuderia; } public String getScuderia() { return scuderia; } public void setPilota(Pilota pilota) { this.pilota = pilota; } public Pilota getPilota() { return pilota; } public String dammiDettagli() { return getPilota().getNome() + " su " + getScuderia(); } }
-
Esercizio 5.c)
Modificatori d'accesso,
static
, epackage
, Vero o Falso:1. Una classe dichiarata
private
non può essere utilizzata fuori dal package in cui è dichiarata.
2. La seguente dichiarazione di classe è scorretta:
3. La seguente dichiarazione di classe è scorretta:public static class Classe {...}
4. La seguente dichiarazione di metodo è scorretta:protected class Classe {...}
public void static metodo () {...}
5. Un metodo statico può utilizzare solo variabili statiche,locali e parametri e non bisogna per forza istanziare un oggetto dalla classe in cui è definito per utilizzarlo.
6. Se un metodo è dichiaratostatic
, non può essere chiamato al di fuori del proprio package.
7. Una classestatic
non è accessibile fuori dal package in cui è dichiarata.
8. Un metodoprotected
viene ereditato in ogni sottoclasse qualsiasi sia il suo package.
9. Una variabilestatic
viene condivisa da tutte le istanze della classe a cui appartiene.
10. Se non anteponiamo modificatori ad un metodo, il metodo è accessibile solo all'interno dello stesso package.
Soluzione
1. Falso,
private
non si può utilizzare con la dichiarazione di una classe.
2. Vero,static
non si può utilizzare con la dichiarazione di una classe.
3. Vero,protected
non è applicabile a classi.
4. Vero,static
deve essere posizionato prima delvoid
.
5. Vero.
6. Falso,static
non è un modificatore d'accesso.
7. Falso,static
non è applicabile a classi.
8. Vero.
9. Vero.
10. Vero. -
Esercizio 5.d) Object Orientation in Java (pratica), Vero o Falso:
1. Un metodo statico deve essere per forza pubblico.
2. L'implementazione dell'incapsulamento implica l'utilizzo delle parole chiaveset
eget
.
3. Per utilizzare le variabili incapsulate di una superclasse in una sottoclasse bisogna dichiararle almenoprotected
.
4. I metodi dichiarati privati non vengono ereditati nelle sottoclassi.
5. Un inizializzatore d'istanza viene invocato prima di ogni costruttore.
6. Una variabile privata risulta disponibile (tecnicamente come se fosse pubblica) tramite l'operatore dot, a tutte le istanze della classe in cui è dichiarata.
7. La parola chiavethis
permette di referenziare i membri di un oggetto che sarà creato solo al runtime all'interno dell'oggetto stesso.
8. La parola chiavethis
è sempre facoltativa.
9. La parola chiavethis
permette di chiamare un costruttore, da un metodo della stessa classe con la sintassithis()
. È necessario però che questa sia la prima istruzione del metodo.
10. Il singleton pattern permette di creare una classe che può essere istanziata una sola volta.
Soluzione
1. Falso.
2. Falso, non si tratta di parole chiave ma solo di una convenzione.
3. Falso, possono essere private ed essere utilizzate tramite i metodi accessor e mutator.
4. Vero.
5. Vero.
6. Vero.
7. Vero.
8. Falso, nel caso ci sia ambiguità tra nomi di variabili d'istanza e locali, la parola chiavethis
è fondamentale (cfr. paragrafo 5.2.2).
9. Falso, solo un altro costruttore della stessa classe può usare quella sintassi.
10. Vero. -
Esercizio 5.e)
Astrarre il concetto di
Moneta
con una classe (completa di commenti). Assumiamo che tutte le monete avranno come valuta l'EURO, ed avranno la variabile d'istanza incapsulatavalore
. Ha senso creare una moneta senza specificarne il valore? Creare un vincolo affinché le monete possano essere istanziate obbligatoriamente con un valore.Conviene stampare una frase in ogni metodo importante per potere verificare l'avvenuta esecuzione del nostro codice. Per esempio quando si istanzia una moneta. Questo vale anche per i prossimi esercizi.
Soluzione
Il listato potrebbe essere simile al seguente:
/** * Questa classe astrae il concetto di Moneta (esercizio 5.e) * * @author Claudio De Sio Cesari */ public class Moneta { /** * La valuta è una costante impostata al valore EURO. */ public final static String VALUTA = "EURO"; /** * Rappresenta il valore della moneta in centesimi. */ private int valore; /** * Costruttore che prende in input il valore della moneta. * * @param valore il valore della moneta. */ public Moneta(int valore) { this.valore = valore; System.out.println("Creata una moneta da " + valore + " centesimi"); } /** * Imposta la variabile d'istanza valore. * * @param valore contiene il valore a cui deve essere impostato il valore * della variabile d'istanza valore. */ public void setValore(int valore) { this.valore = valore; } /** * Restituisce il valore della variabile d'istanza valore. * * @return * il valore della variabile d'istanza valore. */ public int getValore() { return valore; } }
È sufficiente un costruttore per specificare il vincolo richiesto. Inoltre la valuta, essendo fissa per tutta le monete, è stata dichiarata come costante statica. -
Esercizio 5.f)
Considerando la classe
Moneta
creata nell'esercizio precedente, è corretto creare la variabilevalore
incapsulata con i metodisetValore
egetValore
? Modificare la classe in modo tale da astrarre al meglio la classe.
Soluzione
Nell'attuale situazione, dove le specifiche hanno richiesto solo di creare una classe che abbia il vincolo di essere istanziata sempre con un valore, la domanda potrebbe risultare ambigua. Tuttavia non avendo altri vincoli espliciti, è ragionevole pensare di avere i vincoli che esistono nel mondo reale. Una moneta che ha valore specificato (supponiamo 5 centesimi) non potrà mai cambiare il proprio valore. Quindi sembra superfluo quanto meno il metodo setValore. Quindi sarebbe corretto rimuoverlo. È anche consigliabile rendere la variabile
final
per rinforzare il concetto di immutabilità. Di seguito il listato modificato:
/** * Questa classe astrae il concetto di Moneta. * * @author Claudio De Sio Cesari */ public class Moneta { /** * La valuta è una costante impostata al valore EURO. */ public final static String VALUTA = "EURO"; /** * Rappresenta il valore della moneta in centesimi. */ private final int valore; /** * Costruttore che prende in input il valore della moneta. * * @param valore il valore della moneta. */ public Moneta(int valore) { this.valore = valore; System.out.println("Creata una moneta da " + valore + " centesimi di " + VALUTA); } /** * Restituisce il valore della variabile d'istanza valore. * * @return * il valore della variabile d'istanza valore. */ public int getValore() { return valore; } }
Il codice è più compatto, ma forse almeno per l'inizio conviene utilizzare le variabili per meglio memorizzare le definizioni. -
Esercizio 5.g)
Creare una classe
TestMonete
con un metodomain
che istanzia una moneta da 20 centesimi e una moneta da 1 centesimo ed eseguire l'applicazione. C'è qualcosa che non va su quanto viene stampato? Se è così, cambiare il codice in modo tale che le stampe siano senza errori grammaticali.
Soluzione
Il listato della classe
TestMonete
potrebbe essere il seguente:
public class TestMonete { public static void main(String args[]) { Moneta monetaDaVentiCentesimi = new Moneta(20); Moneta monetaDaUnCentesimo = new Moneta(1); } }
Eseguendo quest'applicazione l'output sarà:
Creata una moneta da 20 centesimi di EURO Creata una moneta da 1 centesimi di EURO
Ma sarebbe più giusto che nella seconda riga la parola "centesimi" sia al singolare. Per risolvere questo problema potremmo modificare la classeMoneta
nel seguente modo (riportiamo solo il costruttore responsabile della stampa e un metodo di utilità):
public Moneta(int valore) { this.valore = valore; System.out.println("Creata una moneta da " + formattaUnitaDiMisura(valore) + VALUTA); } private static String formattaUnitaDiMisura(int valore) { return valore + (valore == 1 ? " centesimo di " : " centesimi di "); }
Abbiamo delegato ad un nuovo metodo d'utilità privato la formattazione di un pezzo della frase da stampare usando un semplice operatore ternario (cfr. paragrafo 4.3.4), ed abbiamo risolto il nostro baco. Ora rieseguendo la classeTestMonete
otterremo il seguente output:
Creata una moneta da 20 centesimi di EURO Creata una moneta da 1 centesimo di EURO
-
Esercizio 5.h)
Nella classe
TestMonete
istanziare anche una moneta da 1 euro. Probabilmente ci sarà un altro baco nella stampa, risolverlo. Aggiungere inoltre un metodogetDescrizione
nella classeMoneta
, che ritorna una stringa descrittiva della moneta corrente.
Soluzione
Il listato della classe
TestMonete
dovrebbe solo essere arricchito da un'istruzione di questo tipo:
Moneta monetaDaUnEuro = new Moneta(100);
L'esecuzione di questa applicazione genererà:
Creata una moneta da 20 centesimi di EURO Creata una moneta da 1 centesimo di EURO Creata una moneta da 100 centesimi di EURO
Ma sarebbe più giusto che nella terza riga "100 centesimi di EURO" fosse "1 EURO". Per risolvere questo problema potremmo modificare la classeMoneta
nel seguente modo (riportiamo solo come cambiare il metodo di utilità):
private static String formattaStringaDescrittiva (int valore) { String stringaFormattata = " centesimi di "; if (valore == 1) { stringaFormattata = " centesimo di "; } else if (valore > 99){ stringaFormattata = " "; valore /= 100; } return valore + stringaFormattata; }
Abbiamo modificato il metodo d'utilità privato introdotto nell'esercizio precedente. Non è stato possibile usare l'operatore ternario, quindi abbiamo usato un costruttoif
, ed abbiamo risolto il nostro baco. Ora rieseguendo la classeTestMonete
otterremo il seguente output:
Creata una moneta da 20 centesimi di EURO Creata una moneta da 1 centesimo di EURO Creata una moneta da 1 EURO
Il metodogetDescrizione
quindi potrebbe essere codificato così:
/** * Ritorna una descrizione della moneta corrente. * * @return * una descrizione della moneta corrente. */ public String getDescrizione() { String descrizione = "moneta da " + formattaStringaDescrittiva(valore) + VALUTA; return descrizione; }
E quindi anche il costruttore potrebbe riusare questo metodo nel seguente modo:
public Moneta(int valore) { this.valore = valore; System.out.println("Creata una " + getDescrizione()); }
-
Esercizio 5.i)
Creare una classe (completa di commenti)
PortaMonete
che astrae il concetto di portamonete. Questo deve poter contenere al massimo 10 monete (la classeMoneta
dovrebbe essere già stata creata nell'esercizio precedente). Per ora creare solo un costruttore che consenta di impostare il contenuto delle monete.Anche in questo caso conviene stampare la descrizione delle azioni che si stanno invocando.
Soluzione
Il listato della classe
PortaMonete
potrebbe essere il seguente:
/** * Astrae il concetto di portamonete che può contenere un numero limitato di * monete. * * @author Claudio De Sio Cesari */ public class PortaMonete { /** * Un array che contiene un numero limitato di monete. */ private final Moneta[] monete = new Moneta[10]; /** * Crea un oggetto portamonete contenente monete i cui valori sono * specificati dal varargs valori. * * @param valori * un varargs di valori di monete. */ public PortaMonete(int... valori) { int numeroMonete = valori.length; for (int i = 0; i < numeroMonete; i++) { if (i >= 10) { System.out.println( "Sono state inserite solo le prime 10 monete!"); break; } monete[i] = new Moneta(valori[i]); } } }
Si noti che abbiamo usato un array di 10 oggettiMoneta
dichiaratofinal
, che farà da contenitore delle nostre monete. Inoltre abbiamo usato un varargs valori, per impostare il contenuto delPortaMonete
. Questo ci sarà comodo quando effettivamente creeremo oggetti portamonete.
Abbiamo già introdotto il concetto di varargs nella soluzione del paragrafo 4.v e sarà spiegato in dettaglio nel libro nel paragrafo 7.2.2.Nel caso siano passati più di dieci valori al costruttore, questo imposterà solo i primi dieci e stamperà un messaggio di avvertimento. -
Esercizio 5.j)
Nella classe
TestMonete
istanziare anche un oggettoPortaMonete
con 8 monete e uno con 11 monete, verificare che tutto funzioni correttamente. Aggiornare eventualmente anche il commento della classe.
Soluzione
Il listato della classe
TestMonete
modificato dovrebbe essere qualcosa di simile al seguente:
/** * Classe di test per le classi Moneta e PortaMonete. * * @author Claudio De Sio Cesari */ public class TestMonete { public static void main(String args[]) { Moneta monetaDaVentiCentesimi = new Moneta(20); Moneta monetaDaUnCentesimo = new Moneta(1); Moneta monetaDaUnEuro = new Moneta(100); // Creaiamo un portamonete con 8 monete PortaMonete portaMonete = new PortaMonete(2, 5, 100, 10, 50, 10, 100, 200); // Creaiamo un portamonete con 11 monete PortaMonete portaMoneteInsufficiente = new PortaMonete(2, 5, 100, 10, 50, 10, 100, 200, 10, 5, 2); } }
L'output dovrebbe essere il seguente:
Creata una moneta da 20 centesimi di EURO Creata una moneta da 1 centesimo di EURO Creata una moneta da 1 EURO Creata una moneta da 2 centesimi di EURO Creata una moneta da 5 centesimi di EURO Creata una moneta da 1 EURO Creata una moneta da 10 centesimi di EURO Creata una moneta da 50 centesimi di EURO Creata una moneta da 10 centesimi di EURO Creata una moneta da 1 EURO Creata una moneta da 2 EURO Creata una moneta da 2 centesimi di EURO Creata una moneta da 5 centesimi di EURO Creata una moneta da 1 EURO Creata una moneta da 10 centesimi di EURO Creata una moneta da 50 centesimi di EURO Creata una moneta da 10 centesimi di EURO Creata una moneta da 1 EURO Creata una moneta da 2 EURO Creata una moneta da 10 centesimi di EURO Creata una moneta da 5 centesimi di EURO Sono state inserite solo le prime 10 monete!
-
Esercizio 5.k)
Creare un metodo
aggiungi
all'interno della classe che permetta di aggiungere una moneta al portamonete. Prevedere gli appositi controlli di consistenza.
Soluzione
Il codice risolutore proposto crea come soluzione anche un metodo privato d'utilità che restituisce il primo indice dell'array libero per contenere la nuova moneta:
/** * Aggiunge una moneta al portamonete. Se questo è pieno la moneta non è * aggiunta e viene stampato un errore significativo. * * @param moneta * la moneta da aggiungere. */ public void aggiungi(Moneta moneta) { System.out.println("Proviamo ad aggiungere una " + moneta.getDescrizione()); int indiceLibero = primoIndiceLibero(); if (indiceLibero == -1) { System.out.println("Portamonete pieno! La moneta " + moneta.getDescrizione() + " non è stata aggiunta..."); } else { monete[indiceLibero] = moneta; System.out.println("E' stata aggiunta una " + moneta.getDescrizione()); } } /** * Restituisce il primo indice libero nell'array delle monete o -1 se il * portamonete è pieno. * * @return * il primo indice libero nell'array delle monete o -1 se il * portamonete è pieno. */ private int primoIndiceLibero() { int indice = -1; for (int i = 0; i < 10; i++) { if (monete[i] == null) { indice = i; break; } } return indice; }
-
Esercizio 5.l)
Creare un metodo
stato
che stampi l'attuale contenuto del portamonete.
Soluzione
Il listato per il metodo
stato
potrebbe essere il seguente:
/** * Stampa il contenuto del portamonete. */ public void stato() { System.out.println("Il portamonete contiene:"); for (Moneta moneta : monete) { if (moneta == null) { break; } System.out.println("Una " + moneta.getDescrizione()); } }
-
Esercizio 5.m)
Creare un metodo
preleva
all'interno della classe che consenta di prelevare (e quindi rimuovere) una moneta dal portamonete. Prevedere gli appositi controlli di consistenza.
Soluzione
Il listato per il metodo
preleva
potrebbe essere il seguente (anche in questo caso abbiamo creato un metodo privato di utilità):
/** * Esegue un prelievo della moneta specificata dal portamonete corrente. Nel * caso non sia presente la moneta specificata, un errore significativo * verrà stampato e null verrà ritornato. * * @param moneta * la moneta da prelevare. * @return * la moneta trovata o null se non trovata. */ public Moneta preleva(Moneta moneta) { System.out.println("Proviamo a prelevare una " + moneta.getDescrizione()); Moneta monetaTrovata = null; int indiceMonetaTrovata = indiceMonetaTrovata(moneta); if (indiceMonetaTrovata == -1) { System.out.println("Moneta non trovata!"); } else { monetaTrovata = moneta; monete[indiceMonetaTrovata] = null; System.out.println("Una " + moneta.getDescrizione() + " prelevata"); } return monetaTrovata; } private int indiceMonetaTrovata(Moneta moneta) { int indiceMonetaTrovata = -1; for (int i = 0; i < 10; i++) { if (monete[i] == null) { continue; } int valoreMonetaNelPortaMoneta = monete[i].getValore(); int valore = moneta.getValore(); if (valore == valoreMonetaNelPortaMoneta) { indiceMonetaTrovata = i; break; } } return indiceMonetaTrovata; }
-
Esercizio 5.n)
Modificare la classe
TestMonete
in modo tale da testare nella maniera più completa le classi create.
Soluzione
Come soluzione proponiamo un codice che cerca di testare anche le situazioni di errore:
/** * Classe di test per la classe Moneta. * * @author Claudio De Sio Cesari */ public class TestMonete { public static void main(String args[]) { Moneta monetaDaVentiCentesimi = new Moneta(20); Moneta monetaDaUnCentesimo = new Moneta(1); Moneta monetaDaUnEuro = new Moneta(100); // Creaiamo un portamonete con 11 monete PortaMonete portaMoneteInsufficiente = new PortaMonete(2, 5, 100, 10, 50, 10, 100, 200, 10, 5, 2); // Creaiamo un portamonete con 8 monete PortaMonete portaMonete = new PortaMonete(2, 5, 100, 10, 50, 10, 100, 200); portaMonete.stato(); // Aggiungiamo una moneta da 20 centesimi portaMonete.aggiungi(monetaDaVentiCentesimi); // Aggiungiamo la decima moneta da 1 centesimo. portaMonete.aggiungi(monetaDaUnCentesimo); // Aggiungiamo l'undicesima moneta (dovremmo ottenere un errore e la // moneta non sarà aggiunta) portaMonete.aggiungi(monetaDaUnEuro); // Valutiamo lo stato del portamonete. portaMonete.stato(); // preleviamo 20 centesimi portaMonete.preleva(monetaDaVentiCentesimi); // Aggiungiamo l'undicesima moneta (dovremmo ottenere un errore e la // moneta non sarà aggiunta) portaMonete.aggiungi(monetaDaUnEuro); portaMonete.stato(); //Cerchiamo una moneta non esistente (dovremmo ottenere una stampa di // errore) portaMonete.preleva(new Moneta(7)); } }
L'output dovrebbe essere il seguente:
Creata una moneta da 20 centesimi di EURO Creata una moneta da 1 centesimo di EURO Creata una moneta da 1 EURO Creata una moneta da 2 centesimi di EURO Creata una moneta da 5 centesimi di EURO Creata una moneta da 1 EURO Creata una moneta da 10 centesimi di EURO Creata una moneta da 50 centesimi di EURO Creata una moneta da 10 centesimi di EURO Creata una moneta da 1 EURO Creata una moneta da 2 EURO Creata una moneta da 10 centesimi di EURO Creata una moneta da 5 centesimi di EURO Sono state inserite solo le prime 10 monete! Creata una moneta da 2 centesimi di EURO Creata una moneta da 5 centesimi di EURO Creata una moneta da 1 EURO Creata una moneta da 10 centesimi di EURO Creata una moneta da 50 centesimi di EURO Creata una moneta da 10 centesimi di EURO Creata una moneta da 1 EURO Creata una moneta da 2 EURO Il portamonete contiene: Una moneta da 2 centesimi di EURO Una moneta da 5 centesimi di EURO Una moneta da 1 EURO Una moneta da 10 centesimi di EURO Una moneta da 50 centesimi di EURO Una moneta da 10 centesimi di EURO Una moneta da 1 EURO Una moneta da 2 EURO Proviamo ad aggiungere una moneta da 20 centesimi di EURO E' stata aggiunta una moneta da 20 centesimi di EURO Proviamo ad aggiungere una moneta da 1 centesimo di EURO E' stata aggiunta una moneta da 1 centesimo di EURO Proviamo ad aggiungere una moneta da 1 EURO Portamonete pieno! La moneta moneta da 1 EURO non è stata aggiunta... Il portamonete contiene: Una moneta da 2 centesimi di EURO Una moneta da 5 centesimi di EURO Una moneta da 1 EURO Una moneta da 10 centesimi di EURO Una moneta da 50 centesimi di EURO Una moneta da 10 centesimi di EURO Una moneta da 1 EURO Una moneta da 2 EURO Una moneta da 20 centesimi di EURO Una moneta da 1 centesimo di EURO Proviamo a prelevare una moneta da 20 centesimi di EURO Una moneta da 20 centesimi di EURO prelevata Proviamo ad aggiungere una moneta da 1 EURO E' stata aggiunta una moneta da 1 EURO Il portamonete contiene: Una moneta da 2 centesimi di EURO Una moneta da 5 centesimi di EURO Una moneta da 1 EURO Una moneta da 10 centesimi di EURO Una moneta da 50 centesimi di EURO Una moneta da 10 centesimi di EURO Una moneta da 1 EURO Una moneta da 2 EURO Una moneta da 1 EURO Una moneta da 1 centesimo di EURO Creata una moneta da 7 centesimi di EURO Proviamo a prelevare una moneta da 7 centesimi di EURO Moneta non trovata!
-
Esercizio 5.o)
Il diagramma in figura 5.o.1 potrebbe rappresentare il processo presentato.
Soluzione
Il diagramma in figura 5.o.1 potrebbe rappresentare il processo presentato.
Figura 5.o.1 - Class diagram del package com.claudiodesio.autenticazione.
Si possono notare le barre di sincronizzazione che ci sono servite per mostrare il possibile parallelismo tra le attività di analisi/progettazione e l'architettura. -
Esercizio 5.p)
Problem statement: creare un programma di autenticazione, che richiede all'utente username e password, e gli garantisce l'accesso (basterà stampare un messaggio di benvenuto con il nome dell'utente), se le credenziali inserite sono corrette. Il sistema deve supportare l'autenticazione con almeno tre coppie username e password, con cui ci si può loggare. Eseguire l'analisi dei casi d'uso ed individuare i vari casi d'uso, seguendo il processo descritto nell'approfondimento 5.7 nei paragrafi 5.7.2 e 5.7.2.1.
Esistono vari strumenti software che supportano i diagrammi UML. Ognuno di essi ha la sua filosofia e la sua complessità. Dovreste sceglierne uno prima o poi (magari dopo aver fatto qualche confronto), ma per ora va benissimo anche usare un foglio di carta, una matita, e una gomma da cancellare. La scelta di un tool, capire come funziona etc. richiede del tempo.
Soluzione
In realtà esiste un unico caso d'uso, che abbiamo chiamato "Login" (vedi figura 5.r.1). Infatti le interazioni che l'utente avrà con il sistema, si limitano alle attività relative all'autenticazione, come l'inserimento della username e della password. Anche pensando a diverse tipologie di flussi che possono realizzare l'autenticazione, il compito finale è unico quello di autenticarsi.
Figura 5.p.1 – Use case diagram.
Nel nostro caso, abbiamo trovato un unico caso d'uso, ma questo non significa che sarà sempre così, e che quindi bisogna evitare l'analisi degli use case. A nostro giudizio essa è indispensabile anche per i programmi più banali. -
Esercizio 5.q)
Individuare gli scenari dei casi d'uso del precedente esercizio, seguendo i consigli del paragrafo 5.7.2.2 dell'approfondimento 5.7.
Soluzione
L'analisi degli scenari è molto soggettiva. Nel momento in cui la scriviamo, stiamo decidendo "cosa" deve fare l'applicazione, un qualcosa tutt'altro che scontata.
Partiamo dal prerequisito che non abbiamo ancora studiato le interfacce grafiche (a cui sono dedicati due ultimi capitoli del libro), e quindi abbiamo pensato di realizzare un programma che funzioni solo da riga di comando.
Un altro prerequisito è che il sistema ha precaricato staticamente alcune coppie di username e password valide.
Scenario Principale
1. Il sistema chiede all'utente di inserire la username.
2. L'utente inserisce la username.
3. Il sistema verifica che la username sia valida, e chiede di inserire la password.
4. L'utente inserisce la password.
5. Il sistema controlla che la password sia valida e risponde con un messaggio di conferma di avvenuta autenticazione, utilizzando il vero nome dell'utente che si è autenticato.Come già detto questa è solo una delle possibili soluzioni. Potremmo anche pensare di specificare insieme la username e la password, convalidare l'autenticazione con un codice captcha, avvertire l'utente se il tasto "CAPS LOCK" ("BLOCCO MAIUSCOLO") è inserito, mascherare i caratteri della password con asterischi, richiedere all'utente se vuole memorizzare la username per il prossimo accesso e così via. Abbiamo scelto un'interazione semplice.Scenario 2:
1. Il sistema chiede all'utente di inserire la username.
2. L'utente inserisce la username.
3. Il sistema non riconosce la username inserita, stampa un messaggio e torna al punto 1.
Scenario 3:
1. Il sistema chiede all'utente di inserire la username.
2. L'utente inserisce la username.
3. Il sistema verifica che la username sia valida, e chiede di inserire la password.
4. L'utente inserisce la password.
5. Il sistema non riconosce la password inserita, stampa un messaggio e torna al punto 1.
Definendo questi tre banali scenari, ora abbiamo ben chiaro cosa fare. -
Esercizio 5.r)
Continuiamo l'esercizio precedente seguendo il processo descritto nell'approfondimento 5.7. Essendo una semplice applicazione desktop, sembra superfluo creare un diagramma di deployment. Ma se pensiamo a come poter un giorno riutilizzare una parte di quest'applicazione (d'altronde stiamo parlando di un'applicazione che permette di autenticarsi in un sistema, e che potremmo integrare anche nel caso di studio introdotto nell'approfondimento 5.7 per loggarsi nell'applicazione Logos), allora potrebbe essere molto utile realizzare una visione dall'alto che ne specifichi le dipendenze tra i vari componenti del software. Quindi proviamo a creare un component diagram (o volendo un deployment diagram), creando componenti che abbiano nomi indicativi, e che poi provvederemo a realizzare con le classi. Deve essere giusto un banale diagramma (high level deployment diagram), che esalti la possibilità che una certa parte del software sia riutilizzabile.
È possibile prendere spunto dal diagramma definito nel paragrafo 5.7.3 nell'approfondimento 5.7.
Soluzione
Con il diagramma della figura 5.r.1, evidenziamo come creeremo un componente software che contiene le classi che realizzano l'autenticazione separato dalla classe del
main
. L'unico strumento che attualmente conosciamo per separare le classi sono i package, quindi potremo usare package diversi.
Figura 5.r.1 – Architettura dell'applicazione espressa con un high level deployment diagram.
Consultando l'appendice E potete anche imparare a creare il file JAR contenente le classi del componente dell'autenticazione. In questo modo sarà più semplice poterlo riutilizzare in un secondo momento trattandosi di un unico file. In realtà la traccia dell'esercizio E.a relativo a quell'appendice, richiede proprio che sia creato il file JAR con le classi di questo esercizio. -
Esercizio 5.s)
Continuiamo l'esercizio precedente seguendo il processo descritto nel paragrafo 5.7.4 dell'approfondimento 5.7. Passiamo quindi ad individuare le classi candidate, e di conseguenza le key abstraction.
Soluzione
Seguendo il processo descritto nell'approfondimento 5.7, per poter trovare la lista delle key abstraction, dobbiamo dapprima stilare la lista delle candidate classes. Segue la nostra lista con relativi commenti:
• Autenticazione: potrebbe essere una classe a cui diamo la responsabilità di gestire la funzionalità principale dell'applicazione.
• Login: sembra più il nome del metodo principale dell'applicazione. Potrebbe essere un metodo all'interno di Autenticazione, o di un oggetto dichiarato all'interno di Autenticazione.
• Utente: potrebbe essere l'entità che contiene le informazioni per l'autenticazione.
• Sistema: troppo generico, non sembra essere il nome giusto per una classe.
• Username: potrebbe essere una proprietà della classe Utente.
• Password: potrebbe essere una proprietà della classe Utente.
• Nome: potrebbe essere una proprietà della classe Utente.
• Verifica: potrebbe essere un metodo di Autenticazione, o di un oggetto dichiarato all'interno di Autenticazione.
• Inserimento: potrebbe definire un metodo, ma non sembra un'astrazione chiave.
• Messaggio: per ora sembra solo una stringa, non di certo un'astrazione chiave. Questo non esclude che possa diventare una classe in seguito.
• Sostituzione: potrebbe definire un metodo, ma non sembra un'astrazione chiave.
Quindi la lista delle astrazioni chiave, per ora si limita solo alle uniche due classi della lista in grassetto:Autenticazione
eUtente
. A queste aggiungiamo una classeAutenticazioneLauncher
che contiene solo il metodomain
, e che ha la sola responsabilità di avviare l'applicazione istanziando i giusti oggetti e chiamando i giusti metodi.Ricordiamo al lettore che la scelta di queste classi, come i precedenti passi, dipendono dell'esperienza, delle intenzioni, dal tempo a disposizione, dalla predisposizione, dalla concretezza e dalla forma mentis della persona che svolge l'analisi. Esistono migliaia di soluzioni che possono portare al successo lo sviluppo, ognuna delle quali ha dei pro e dei contro. Il consiglio è quello di orientarsi (soprattutto nei primi tempi) sulla soluzione più semplice possibile. -
Esercizio 5.t)
Continuiamo l'esercizio precedente verificando la fattibilità degli scenari individuati, creando sequence diagram basati sulle interazioni delle classi individuate, come descritto nel paragrafo 5.7.5 dell'approfondimento 5.7.
Soluzione
Seguendo il processo descritto nell'approfondimento 5.7, dobbiamo ora verificare la definizione superficiale delle key abstraction, con diagrammi di interazione (cfr. approfondimento 4.6), ovvero collaboration o sequence diagram. Questi due diagrammi sono equivalenti, tanto che molti tool UML, permettono di trasformare un collaboration diagram in un sequence diagram e viceversa con la pressione di un tasto. In particolare un sequence diagram mostra le interazioni tra gli oggetti in un determinato lasso di tempo, enfatizzando la sequenza dei messaggi che le entità si scambiano. Un collaboration diagram invece, come il sequence diagram mostra le interazioni tra gli oggetti in un determinato lasso di tempo, ma enfatizza l'organizzazione strutturale delle entità che interagiscono. Siccome in questo caso ci sembra più interessante enfatizzare la sequenza di messaggi scambiati tra gli oggetti, useremo sequence diagram per descrivere gli scenari descritti nella soluzione dell'esercizio 5.q, usando gli oggetti descritti nell'esercizio 5.s. Dato che questi ultimi oggetti sono solo astrazioni chiave, in questo momento possiamo anche decidere se dobbiamo creare nuove classi, aggiungere, modificare o spostare metodi, rinominare le classi esistenti e così via. Il diagramma, con la sua "visione dell'alto", favorisce l'individuazione di eventuali situazioni errate, migliorabili o fallaci.
Figura 5.t.1 – Sequence diagram che rappresenta lo scenario principale.
Nella figura 5.t.1 ci siamo limitati a trovare un flusso coerente con quello descritto nello scenario principale, utilizzando solo le key abstraction individuate nell'esercizio precedente. La situazione vista così, sembra funzionare.
L'utente dell'applicazione lancia l'applicazione tramite la classeAutenticazioneLauncher
. Questa chiama un metodo che si chiamalogin
sulla classeAutenticazione
. Da qui in poi questo metodo eseguirà delle operazioni. Prima richiama il metodorichiediUsername
, che richiede la username all'utente stampando un messaggio, e aspetta l'input dell'utente.Questo metodo è interno, cioè definito nella stessa classe. Lo si capisce dal fatto che la freccia che lo definisce parte e termina nella stessa classe. In pratica, in UML la chiamata di un metodo è indicata dalla partenza di una "freccia" (che come elemento UML viene chiamato messaggio) da un certo oggetto. La freccia punta all'oggetto dove risiede il metodo chiamato. Quindi una freccia che ritorna sullo stesso oggetto sta ad indicare la chiamata ad un metodo interno.Poi una volta che l'utente inserisce la username, la classeAutenticazione
chiamerà un metodo interno chiamatoverificaUsername
. Tale metodo recupererà l'oggetto utente dalla collezione di utenti che è stata definita per rappresentare la lista di utenti (che implementeremo con un array).Questa parte non è stata rappresentata nel diagramma, per non complicarlo troppo. Avremmo potuto aggiungere un elemento nota per specificare le nostre intenzioni, sarebbe stato più corretto, ma abbiamo evitato visto che dovevamo spiegare con queste righe il diagramma.Quindi anche se l'oggettoUtente
, non compare sul diagramma, è in qualche modo coinvolto. Questo perché nella nostra mente, un utente dovrebbe definire le variabiliusername
,password
enome
, e quindi per verificare se esiste una certa username, ma anche per vedere se una password è associata ad una certa username, bisogna utilizzare un oggettoUtente
.
Il resto del flusso è molto semplice da interpretare. Viene chiamato il metodo internorichiediPassword
, che richiede la password all'utente stampando un messaggio e aspetta l'input dell'utente. Poi una volta che l'utente inserisce la password, la classeAutenticazione
chiamerà un metodo interno chiamatoverificaPassword
. (ed anche in questo caso la verifica verrà fatta utilizzando un oggettoUtente
). Infine viene stampato il messaggio "Benvenuto" specificando il nome dell'utente che si è loggato (che si prende dall'oggetto utente che è stato usato per convalidare l'autenticazione).
Figura 5.t.2 – Sequence diagram che rappresenta il secondo scenario.
Possiamo notare che per quello che abbiamo progettato con il diagramma in figura 5.t.2, e nello scenario relativo, la username verrà richiesta sin quando non verrà immessa una username valida. Stesso discorso per la password come è possibile osservare nella figura 5.t.3 che mostra un sequence diagram relativo al terzo scenario individuato.
Figura 5.t.3 – Sequence diagram che rappresenta il terzo scenario. -
Esercizio 5.u)
In base ai passi fatti negli esercizi 5.p, 5.q, 5.r, 5.s, e 5.t, implementare una soluzione funzionante, senza utilizzare l'incapsulamento. Tale soluzione potrebbe impattare sulla nostra analisi, nel senso che potrebbe convalidare alcune scelte fatte o smentendole. Il nostro obiettivo rimane quello di creare del codice funzionante, quindi se qualche passo dell'analisi, non sembra fattibile con la programmazione, dobbiamo dare precedenza alla implementazione.
Soluzione
Il listato che è scaturito dalla nostra analisi, non è proprio quello che ci aspettavamo. La classe
Utente
potrebbe essere definita così:package com.claudiodesio.autenticazione; public class Utente { public String nome; public String username; public String password; public Utente(String n, String u, String p) { this.nome = n; this.username = u; this.password = p; } }
Mentre questa è la classe
Autenticazione
:
package com.claudiodesio.autenticazione; import java.util.*; public class Autenticazione { private static final Utente[] utenti = { new Utente("Daniele", "dansap", "musica"), new Utente("Giovanni", "giobat", "scienze"), new Utente("Ligeia", "ligder", "arte") }; public static void main(String args[]) { Scanner scanner = new Scanner(System.in); while (true) { System.out.println("Inserisci username."); String username = scanner.nextLine(); Utente utente = verificaUsername(username); if (utente == null) { System.out.println("Utente non trovato!"); continue; } System.out.println("Inserisci password"); String password = scanner.nextLine(); if (password != null && password.equals(utente.password)) { System.out.println("Benvenuto " + utente.nome); break; } else { System.out.println("Autenticazione fallita"); } } } private static Utente verificaUsername(String username) { if (username != null) { for (Utente utente : utenti) { if (username.equals(utente.username)) { return utente; } } } return null; } }
Nel momento in cui abbiamo scritto il codice, abbiamo trovato alcune difficoltà. La prima è stata implementare l'algoritmo descritto negli scenari: abbiamo dovuto introdurre un ciclo infinito, e i comandi
break
econtinue
, non proprio la soluzione che ci aspettavamo.
Abbiamo preso anche un'altra decisione in contrasto con la nostra analisi: eliminare la classeAutenticazioneLauncher
che avrebbe dovuto contenere solo il metodomain
. Questo perché sembrava una decisione forzata e priva di una qualche utilità.
Un altro dubbio ci è sorto nel momento in cui abbiamo dovuto verificare la correttezza della username e della password, è quello che non avevamo deciso in fase di progettazione se la verifica doveva tener conto delle lettere maiuscole o minuscole.
Non siamo riusciti neanche a creare un metodoverificaPassword
, complementare alverificaUsername
visto che se avessimo creato un metodo a parte, non avremmo potuto utilizzare la clausolabreak
per uscire dal ciclo infinito. Insomma, rispetto all'analisi che abbiamo fatto, ci sono stati dei problemi che abbiamo risolto direttamente con il codice. Ma da dove sono scaturiti questi problemi? Perché il nostro processo non ha funzionato come speravamo?
La risposta è che essenzialmente ci mancano ancora concetti fondamentali che dobbiamo ancora studiare. E questo esercizio sarà ripreso dopo per essere reso più chiaro ed efficiente. In particolare ci è mancato un passaggio fondamentale, che consiste nell'assegnare le responsabilità alle classi che creiamo, e al passo di creare un diagramma delle classi che ci aiuti a meglio distribuire le responsabilità tra le classi. Ogni oggetto deve avere un'unica responsabilità, o più responsabilità relazionate strettamente tra loro. Le responsabilità verranno implementate o come metodi, o come variabili, e definiscono ruoli che si possono assegnare agli oggetti.
In qualsiasi caso, il programma che abbiamo scritto funziona correttamente. Anche se la nostra analisi non è stata perfetta, ci ha dato delle indicazioni importanti. Per esempio l'analisi degli scenari è stata fondamentale per capire cosa dovevamo fare. E i diagrammi di interazione ci hanno anche indirizzato verso la soluzione di codice (poi parzialmente disattesa)
Dopo l'esperienza fatta con questi esercizi, se nel caso non siate riuscite a risolvere gli esercizi 4.m, 4.q, 4.r, 4.s, 4.t, 4.u, 4.v e 4.z del capitolo precedente, potreste provare a riprogettarli da zero e cercare di trovare una soluzione da soli, magari diversa da quella proposta). -
Esercizio 5.v)
Incapsulare la classe
Utente
dell'esercizio 5.u, e modificare di conseguenza la classeAutenticazione
affinché tutto continui a funzionare.
Soluzione
Il listato di
Utente
incapsulato è il seguente:package com.claudiodesio.autenticazione; public class Utente { private String nome; private String username; private String password; public Utente(String n, String u, String p) { this.nome = n; this.username = u; this.password = p; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } }
mentre
Autenticazione
cambia poco: bisogna solo sostituire l'accesso diretto alle variabili pubbliche diUtente
, con le relative chiamate ai metodi accessor:
package com.claudiodesio.autenticazione; import java.util.Scanner; public class Autenticazione { private static final Utente[] utenti = { new Utente("Daniele", "dansap", "musica"), new Utente("Giovanni", "giobat", "scienze"), new Utente("Ligeia", "ligder", "arte") }; public static void main(String args[]) { Scanner scanner = new Scanner(System.in); while (true) { System.out.println("Inserisci username."); String username = scanner.nextLine(); Utente utente = verificaUsername(username); if (utente == null) { System.out.println("Utente non trovato!"); continue; } System.out.println("Inserisci password"); String password = scanner.nextLine(); if (password != null && password.equals(utente.getPassword())) { System.out.println("Benvenuto " + utente.getNome()); break; } else { System.out.println("Autenticazione fallita"); } } } private static Utente verificaUsername(String username) { if (username != null) { for (Utente utente : utenti) { if (username.equals(utente.getUsername())) { return utente; } } } return null; } }
-
Esercizio 5.w)
Disegnare un class diagram contente le due classi
Utente
eAutenticazione
modificate nell'esercizio precedente che mostrano le loro variabili e i loro metodi. È possibile consultare l'approfondimento 4.7 contenente uno schema di riferimento per la sintassi UML. In particolare per i membri statici (che vanno sottolineati) e la notazione di aggregazione che sussiste tra la classe contenitoreAutenticazione
e la classe contenutaUtente
. Usare anche la notazione di molteplicità. Inoltre includere le due classi nella notazione di package.
Soluzione
La figura 5.w.1 mostra il class diagram richiesto. Si noti come i membri della classe
Autenticazione
siano marcati statici con una notazione costituita da una sottolineatura. Inoltre l'aggregazione (che indica la relazione di contenimento) viene indirizzata dall'oggetto contenuto all'oggetto contenente, e si distingue sintatticamente da una semplice associazione (relazione d'uso) tramite il disegno di un rombo bianco sul lato dell'oggetto contenuto. Il simbolo di asterisco*
accanto all'oggetto contenuto invece, descrive la molteplicità dell'oggetto contenuto.Come abbiamo già notato in precedenza, la notazione di molteplicità che si inserisce nel diagramma delle classi, rappresenta un'informazione relativa alle possibili istanze delle classa coinvolta nella relazione. Ricordiamo che la notazione della molteplicità, viene utilizzata nel diagramma delle classi sulle notazioni di associazione, aggregazione e composizione. A volte, in altri diagrammi come il component diagram, la notazione di molteplicità, viene usata anche sulla notazione della relazione di dipendenza.Si noti infine che sul lato dell'oggetto contenente non ci sono simboli di molteplicità, questo significa che è come se fosse presente la molteplicità di default, ovvero 1. Infatti un oggettoAutenticazione
contiene più oggettiUtente
.
Figura 5.w.1 - Class diagram del package com.claudiodesio.autenticazione. -
Esercizio 5.x)
Abbiamo già notato alla fine dell'esercizio 5.u, che la soluzione di codice implementata non ci soddisfaceva. Infatti durante la nostra analisi avevamo individuato un flusso di esecuzione basato su classi e metodi differenti, come mostrato nei sequence diagram delle soluzioni dell'esercizio 5.t, che riportiamo per comodità.
Figura 5.7.1 - Sequence diagram che rappresenta lo scenario principale.
Figura 5.t.2 - Sequence diagram che rappresenta il secondo scenario.
Figura 5.t.3 - Sequence diagram che rappresenta il terzo scenario.
Creare un class diagram che rappresenti correttamente le classi necessarie a far funzionare gli scenari descritti con i sequence diagram, supportandosi con la syntax reference dell'approfondimento 4.7. Inoltre trasformare la classeAutenticazione
in un singleton, come descritto nel paragrafo 5.4.6.
È possibile anche definire nuovi metodi o variabili se opportuno. Se non si riescono a definire i particolari della classe (per esempio tipi di ritorno, nomi o tipi di parametri e così via, semplicemente non definirli).Ovviamente la classeUtente
deve rimanere incapsulata.
Soluzione
La figura 5.x.1 mostra il class diagram richiesto. Notiamo che per trasformare la classe in singleton, abbiamo definito un costruttore privato, una variabile statica
instance
di tipoAutenticazione
, e un metodogetInstance
che ha la responsabilità di restituire sempre la stessa istanza diAutenticazione
, ovvero l'istanzainstance
, che viene istanziata opportunamente un'unica volta.
Visto che la classe è ora un singleton e verrà istanziata, abbiamo fatto in modo che i suoi metodi e le sue variabili non siano più statici. Abbiamo scritto tutti i metodi che sono stati descritti nei sequence diagram e abbiamo aggiunto i tipi di ritorno e i parametri a nostro avviso più corretti. In realtà scopriremo se questi sono davvero corretti quando affronteremo l'implementazione, questa per ora è solo la nostra idea, ed anche un'idea superficiale (si pensi alla soluzione di codice che abbiamo implementato nell'esercizio 5.u, dove il risultato non è quello a cui volevamo arrivare).
In particolare abbiamo inteso i metodirichiediUsername
erichiediPassword
come metodi di stampa. Infatti non prendono in input parametri e nemmeno dichiarano tipi di ritorno. I metodiverificaUsername
everificaPassword
invece dovrebbero tornare un booleano (true se la verifica va a buon fine e false se non va a buon fine). Abbiamo anche aggiunto i metodiauguraBenvenuto
,autenticazioneFallita
, eusernameInesistente
intesi come metodi di stampa, anche se non sono stati riportati nei sequence diagram (i nomi sono auto-esplicativi). Tutti questi metodi sono metodi privati, mentre l'unico metodo pubblico è il metodologin
, che gestisce il flusso delle chiamate ai metodi privati.
Figura 5.x.1 - Class diagram del package com.claudiodesio.autenticazione modificato come richiesto.
Nella nostra mente la classeAutenticazione
deve funzionare così (per ora). -
Esercizio 5.y)
Una volta implementa la soluzione dell'esercizio precedente, dovremmo avere una classe
Autenticazione
con diversi metodi e responsabilità. Ha un unico metodo pubblico che gestisce il flusso (login
) e diversi metodi privati, alcuni verificano la correttezza dei dati inseriti dall'utente (utilizzando la variabile d'istanza utenti) ed altri per stampare messaggi all'utente.
Un oggetto che si chiamaAutenticazione
è giusto che abbia la responsabilità di:
1. Gestire il flusso di esecuzione dell'applicazione.Le responsabilità identificano l'astrazione della classe, e questa classe è sicuramente troppo carica di responsabilità. Facciamo quindi evolvere il nostro class diagram: cerchiamo di astrarre la classe
2. Verificare la correttezza dei dati inseriti dall'utente.
3. Contenere la lista degli utenti.
4. Stampare messaggi.Autenticazione
, nella maniera più corretta e trovare altre astrazioni (classi) che possano implementare le responsabilità più specifiche. Partiamo dal trovare una classe che contenga i dati su cui vogliamo lavorare. Come la chiamiamo? Che variabili e metodi deve contenere? Siccome dobbiamo pensare in maniera object oriented, cerchiamo di renderla il più possibile riutilizzabile, ma anche coerente con il contesto in cui la stiamo definendo.
Soluzione
Come è possibile verificare in figura 5.y.1, abbiamo creato una nuova classe chiamata
ProfiliUtenti
, che funge da base di dati e che contiene le informazioni sugli utenti (l'array utenti). Abbiamo deciso che un'istanza di questa classe sostituirà l'array utenti che prima risiedeva all'interno della classeAutenticazione
, per non far perdere alla classeAutenticazione
l'informazione sugli utenti. Adesso le classi sono astratte meglio, in quanto ognuna ha un ruolo specifico. Ci siamo resi conto che il design pattern singleton ha più senso che sia implementato nella classeProfiliUtenti
, rispetto che inAutenticazione
. Infatti sono i dati che devono essere unici per tutte le classi, non il processo di autenticazione. Quindi abbiamo agito di conseguenza.
Figura 5.y.1 - Class diagram del package com.claudiodesio.autenticazione conProfiliUtenti
.
-
Esercizio 5.z)
Continuando l'esercizio precedente, modificare il class diagram individuando una classe che abbia le responsabilità di stampare i messaggi a video.
Soluzione
Abbiamo creato una semplice classe di utilità chiamata
Stampa
, contenente tutti metodi che mandano messaggi all'utilizzatore dell'applicazione. Li abbiamo resi tutti statici, perché sembra superfluo istanziare una classe che contenga solo metodi di stampa senza che definisca variabili d'istanza.
Quest'ultima è una scelta come un'altra, non è detto che sia la migliore. Dichiarare statici i metodi implica ignorare i vantaggi dell'estensibilità che vedremo nei prossimi capitoli, ma la nostra scelta non è da condannare.
Figura 5.z.1 - Class diagram del package com.claudiodesio.autenticazione conStampa
.
-
Esercizio 5.aa)
Continuando l'esercizio precedente, la classe
Autenticazione
è ora astratta correttamente? Confermare o modificare ancora il diagramma.
Soluzione
La classe è astratta correttamente? Dipende dai punti di vista. La classe
Autenticazione
, ha come responsabilità la definizione del flusso della login, e la verifica della correttezza dei dati. Messa così sembra vada bene. Tuttavia si potrebbe anche pensare di delegare la verifica della correttezza dei dati ad un'altra classe d'utilità che potremmo chiamareVerificatore
. Questa classe potrebbe contenere i due metodi di verifica dichiarati statici, oppure potrebbe contenere un costruttore a cui passiamo l'istanza diUtente
che vogliamo verificare. Sono scelte tutte valide, ognuna delle quali ha delle conseguenze. Non creare la classeVerificatore
implicherebbe avere una classeAutenticazione
più corposa, ma crearla implicherebbe una classe in più (tra l'altro dipendente strettamente dalla classeUtente
). Per ora optiamo per lasciare le cose come stanno. Decideremo più tardi quando avremo un quadro più chiaro. -
Esercizio 5.bb)
In base alla conclusione dell'esercizio precedente, implementare la soluzione di codice più vicina alla soluzione progettata. Rispetto alla soluzione a cui eravamo arrivati nell'esercizio 5.u, dovremmo avere la stessa funzionalità, ma un codice più semplice con cui interagire, astratto meglio, e più riutilizzabile.
Soluzione
Come abbiamo detto, la classe
Utente
rimane invariata:
package com.claudiodesio.autenticazione; public class Utente { private String nome; private String username; private String password; public Utente(String n, String u, String p) { this.nome = n; this.username = u; this.password = p; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } }
La classeProfiliUtenti
è stata implementata in maniera fedele rispetto a come era stata progettata:
package com.claudiodesio.autenticazione; public class ProfiliUtenti { private static ProfiliUtenti instance; private Utente[] utenti; private ProfiliUtenti() { utenti = creaUtenti(); } public static ProfiliUtenti getInstance() { if (instance == null) { instance = new ProfiliUtenti(); } return instance; } private Utente[] creaUtenti() { Utente[] utenti = { new Utente("Daniele", "dansap", "musica"), new Utente("Giovanni", "giobat", "scienze"), new Utente("Ligeia", "ligder", "arte") }; return utenti; } public void setUtenti(Utente[] utenti) { this.utenti = utenti; } public Utente[] getUtenti() { return utenti; } }
Anche la classeStampa
è fedele a come era stata progettata, a meno dell'introduzione di un metodo privato in più:stampaMessaggio
, che centralizza l'istruzione di stampa. Questo può servirci in futuro se vorremo modificare il modo in cui stampiamo un messaggio, perché dovremo farlo solo in quel metodo e non in tutti gli altri:
package com.claudiodesio.autenticazione; public class Stampa { public static void richiediUsername() { stampaMessaggio("Inserisci username."); } public static void richiediPassword() { stampaMessaggio("Inserisci password."); } public static void auguraBenvenuto(String nome) { stampaMessaggio("Benvenuto " + nome); } public static void usernameInesistente() { stampaMessaggio("Utente non trovato!"); } public static void autenticazioneFallita() { stampaMessaggio("Autenticazione fallita"); } private static void stampaMessaggio(String messaggio) { System.out.println(messaggio); } }
Invece la classeAutenticazione
ha subito dei cambiamenti:
package com.claudiodesio.autenticazione; import java.util.Scanner; public class Autenticazione { public void login() { boolean autorizzato = false; Scanner scanner = new Scanner(System.in); do { Stampa.richiediUsername(); String username = scanner.nextLine(); Utente utente = trovaUtente(username); if (utente != null) { Stampa.richiediPassword(); String password = scanner.nextLine(); if (verificaPassword(utente, password)) { Stampa.auguraBenvenuto(utente.getNome()); autorizzato = true; } else { Stampa.autenticazioneFallita(); } } else { Stampa.usernameInesistente(); } } while (!autorizzato); } private Utente trovaUtente(String username) { Utente[] utenti = ProfiliUtenti.getInstance().getUtenti(); if (username != null) { for (Utente utente : utenti) { if (username.equals(utente.getUsername())) { return utente; } } } return null; } // private boolean verificaUsername(String username) { // Utente[] utenti = ProfiliUtenti.getInstance().getUtenti(); // boolean trovato = false; // Utente utente = trovaUtente(username); // if (utente != null && username.equals(utente.getUsername())) { // trovato = true; // } // return trovato; // } private boolean verificaPassword(Utente utente, String password) { boolean trovato = false; if (password != null) { if (password.equals(utente.getPassword())) { trovato = true; } } return trovato; } public static void main(String args[]) { Autenticazione autenticazione = new Autenticazione(); autenticazione.login(); } }
In particolare, il metodoverificaUsername
, che ritorna un booleano come avevamo progettato, è stato commentato e sostituito con il metodotrovaUtente
, che ritorna direttamente l'oggettoUtente
relativo allo username specificato come parametro. Se non viene trovato nessun utente, il metodo ritornanull
. Questa sostituzione ci permette di non duplicare codice (sia per trovare un utente controvaUtente
). Infatti, per verificare lo username converificaUsername
, avremmo effettuato lo stesso ciclo, e il codice dei due metodi sarebbe stato quasi identico.
Il metodomain
è stato introdotto solo come metodo per testare il funzionamento della funzionalità di login. Poteva esser posizionato in una classe qualsiasi, come per esempioAutenticazioneLauncher
che avevamo inizialmente individuato nella nostra analisi tra le soluzioni degli esercizi del quinto capitolo.
Il metodologin
ora contiene la cosiddetta logica di business, ovvero il codice che soddisfa i requisiti grazie ad un algoritmo opportuno. Rispetto alla soluzione dell'esercizio 5.u, si noti l'eliminazione dei costruttibreak
econtinue
, sostituiti dal più conveniente ciclodo while
, supportato dalla variabile booleanaautorizzato
, che viene impostata atrue
solo nel momento in cui le procedure di verifica dello username e della password sono entrambe verificate. L'algoritmo risulta più chiaro e lineare, anche grazie al supporto della classeStampa
e dell'oggettoScanner
, che vengono utilizzati più volte rispettivamente per stampare messaggi in output e raccogliere input dell'utente dell'applicazione. Tuttavia a nostro parere, dei miglioramenti all'algoritmo e all'astrazione della classe si possono ancora fare. Infatti abbiamo dovuto improvvisare la nostra soluzione, perché quella progettata con il class diagram non si è rivelata meritevole di essere implementata. Cosa è mancato?
È mancato ridisegnare gli scenari con gli interaction diagram, alla luce delle nuove classi e dei nuovi metodi individuati. In questi interaction diagram, avremmo potuto specificare anche i dettagli come tipi di parametri, nomi di oggetti e tipi di ritorno. Infatti i primi sequence diagram che abbiamo creato nell'esercizio 5.t, erano basati solo sulle key abstraction, e servivano per verificare cosa fare (erano diagrammi di analisi). I diagrammi che avremmo potuto creare dopo le modifiche fatte al class diagram invece, avrebbero dovuto essere considerati diagrammi di progettazione che spiegassero come fare.
Per ora un passo in avanti grazie al class diagram lo abbiamo fatto, negli esercizi dei prossimi capitoli cercheremo di farne altri. -
Esercizio 5.cc
Data la seguente classe:
public class BluRay { int capacitaMassimaInGigaByte = 25 byte[] content; BluRay() { } void setContenuto(byte[] contenuto) { this.contenuto = contenuto; } byte[] getContenuto() { return contenuto; } }
Aggiungere i modificatori più appropriati per ogni membro.
Soluzione
La soluzione migliore sembra essere la seguente:
public class BluRay { public final static int capacitaMassimaInGigaByte = 25; private byte[] contenuto; public BluRay() { } public void setContenuto(byte[] contenuto) { this.contenuto = contenuto; } public byte[] getContenuto() { return contenuto; } }
-
Esercizio 5.dd)
Quali delle seguenti dichiarazioni di
import
statici sono valide?1.import static java.lang.*;
2.import static java.lang.Math;
3.import static java.lang.Math.*;
4.import static java.lang.Math.PI;
5.import static java.lang.Math.random();
6.import static java.lang.Math.random;
Soluzione
Le dichiarazioni corrette sono la 3, la 4 e la 6.
-
Esercizio 5.ee)
Qual è l'output del seguente programma?
public class InitTest { { System.out.println("Initializer"); } static { System.out.println("Static Initializer"); } public InitTest () { System.out.println("Constructor"); } public void method() { System.out.println("Method"); } public static void staticMethod() { System.out.println("Static Method"); new InitTest().method(); } public static void main(String args[]) { InitTest.staticMethod(); } }
Soluzione
L'output del programma è:
Static Initializer Static Method Initializer Constructor Method
-
Esercizio 5.ff)
Creare una classe
Libro
incapsulata, in modo tale che astragga il concetto di un libro che può essere venduto in una libreria. Tra i campi definiti dalla classeLibro
, deve esserci il campo genere (inteso come genere letterario). Fare in modo che un libro possa essere associato solo ad un genere letterario compreso tra un insieme di generi letterari predefiniti, per esempio l'insieme costituito dai generi romanzo, thriller, saggio, manuale. Creare una classe che testi che gli oggettiLibro
funzionino nella maniera corretta.
Soluzione
La classe Libro richiesta potrebbe essere la seguente:
public class Libro { private String isbn; private String titolo; private String autore; private int prezzo; private String genere; public static final String [] generi = {"Romanzo", "Saggio", "Thriller", "Manuale"}; public Libro(String isbn, String titolo, String autore, int prezzo, String genere) { setIsbn(isbn); setTitolo(titolo); setAutore(autore); setPrezzo(prezzo); setGenere(genere); } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public String getTitolo() { return titolo; } public void setTitolo(String titolo) { this.titolo = titolo; } public String getAutore() { return autore; } public void setAutore(String autore) { this.autore = autore; } public int getPrezzo() { return prezzo; } public void setPrezzo(int prezzo) { this.prezzo = prezzo; } public String getGenere() { return genere; } public void setGenere(String genere) { for (String generePredefinito : generi) { if (generePredefinito.equals(genere)) { this.genere = genere; return; } } System.out.println("Genere " + genere + " non valido! Usare uno dei seguenti generi:"); for (String generePredefinito : generi) { System.out.println(generePredefinito); } } }
Notare che abbiamo creato un array di stringhe generi come costante statica per definire i generi validi predefiniti. Abbiamo utilizzato questi ultimi per testare la validità della chiamata dal metodo
setGenere
. Notare che l'utilizzo del comandoreturn
utilizzato all'interno del metodo, causa la terminazione immediata dello stesso. Il comandoreturn
non è seguito da nessun valore o variabile perché il metodo ha come tipo di ritornovoid
.
Notare che il controllo definito all'interno del metodoCon la seguente classe abbiamo testato la classesetGenere
, permette di evitare il settaggio del campogenere
nel caso non sia valido il parametro del metodo, ma l'oggetto di tipoLibro
viene comunque creato con la variabile genere settata a null. Nel capitolo 8 vedremo come gestire questo tipo di situazione lanciando un'eccezione, evitando così di istanziare un oggettoLibro
senza settare il campo genere.Libro
:
public class TestLibro { public static void main(String[] args) { Libro jfaVol1 = new Libro("979-12-200-4915-3", "Java for Aliens Vol. 1", "Claudio De Sio Cesari", 25, "Manuale"); Libro jfaVol2 = new Libro("979-12-200-4916-0", "Java for Aliens Vol. 2", "Claudio De Sio Cesari", 25, "Biografia"); System.out.println("Genere JFA Vol 1 = " + jfaVol1.getGenere()); System.out.println("Genere JFA Vol 2 = " + jfaVol2.getGenere()); } }
Segue l'output della precedente classe:
Genere Biografia non valido! Usare uno dei seguenti generi: Romanzo Saggio Thriller Manuale Genere JFA Vol 1 = Manuale Genere JFA Vol 2 = null
-
Esercizio 5.gg)
Tenendo conto dell'esercizio precedente, creare inoltre la classe
Scaffale
. Ogni scaffale deve essere dedicato ad un certo genere letterario compreso nell'elenco dei generi prescelti nel precedente esercizio, e quindi deve contenere solo libri dello stesso genere. Nella classeScaffale
deve esserci un metodo chiamatoaggiungiLibro(Libro libro)
, che permette di aggiungere allo scaffale un libro con il genere corretto. Creare anche la classeLibreria
. Una libreria deve contenere un solo scaffale per ogni genere, e quindi bisogna impedire che possano essere aggiunti due scaffali con lo stesso genere. Inoltre la classeLibreria
deve implementare il pattern Singleton. Infine creare una classeTestLibreria
, che crea un'oggetto di tipoLibreria
aggiungendogli degli oggetti di tipoScaffale
ai quali siano stati aggiunti oggetti di tipoLibro
. Verificare che alla libreria non possano essere aggiunti due scaffali con lo stesso genere, e che ad uno scaffale non possano essere aggiunti libri con generi diversi da quello definito dallo scaffale.
Soluzione
Prima di definire la classe
Scaffale
abbiamo ritenuto necessario ridefinire la classeLibro
nel seguente modo:
public class Libro { private String isbn; private String titolo; private String autore; private int prezzo; private String genere; public Libro(String isbn, String titolo, String autore, int prezzo, String genere) { setIsbn(isbn); setTitolo(titolo); setAutore(autore); setPrezzo(prezzo); setGenere(genere); } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public String getTitolo() { return titolo; } public void setTitolo(String titolo) { this.titolo = titolo; } public String getAutore() { return autore; } public void setAutore(String autore) { this.autore = autore; } public int getPrezzo() { return prezzo; } public void setPrezzo(int prezzo) { this.prezzo = prezzo; } public String getGenere() { return genere; } public void setGenere(String genere) { if (GenereUtils.isGenereValido(genere)) { this.genere = genere; } else { GenereUtils.stampaErrore(genere); } } }
Si può notare che abbiamo semplificato l'implementazione, eliminando l'array generi, visto che siccome dovremo utilizzare il concetto di genere anche per la classeScaffale
, abbiamo preferito creare anche una classe d'utilità che abbiamo chiamataGenereUtils
, che abbiamo utilizzato anche per semplificare l'implementazione dei controlli del metodo setGenere. Riportiamo la classeGenereUtils
di seguito:
public class GenereUtils { public static final String ROMANZO = "Romanzo"; public static final String SAGGIO = "Saggio"; public static final String THRILLER = "Thriller"; public static final String MANUALE = "Manuale"; public static final String FANTASCIENZA = "Fantascienza"; public static final String[] generi = { ROMANZO, SAGGIO, THRILLER, MANUALE, FANTASCIENZA }; public static boolean isGenereValido(String genere) { boolean genereValido = false; for (String generePredefinito : generi) { if (generePredefinito.equals(genere)) { genereValido = true; break; } } return genereValido; } public static void stampaErrore(String genere) { System.out.println("Genere + " + genere + " non valido! " + "Usare uno dei seguenti generi:"); for (String generePredefinito : generi) { System.out.println(generePredefinito); } } }
Nella classeGenereUtils
abbiamo riportato i generi sotto forma di costanti statiche e pubbliche, che se usate al posto delle stringhe permettono di evitare che errori di battitura diventino bug. Ne abbiamo approfittato per aggiungere un nuovo genereFANTASCIENZA
. Inoltre per comodità le abbiamo anche utilizzate per riempire l'array generi, in modo tale da usare i cicli per iterare sugli elementi dell'array. Infine abbiamo dichiarato due metodi statici e pubblici. Il metodoisGenereValido
, ritornatrue
solo se il genere è valido. Il metodostampaErrore
stampa un messaggio di errore. Entrambi questi metodi sono stati utilizzati nel metodogetGenere
della classeLibro
.
La classeScaffale
quindi, l'abbiamo definita nel seguente modo:
public class Scaffale { private Libro[] libri; private String genere; public Scaffale(String genere) { libri = new Libro[100]; setGenere(genere); } public void aggiungiLibro(Libro libro) { if (genere == null) { System.out.println("Il genere di questo scaffale non è stato " + "ancora settato e quindi non possono essere aggiunti libri!"); GenereUtils.stampaErrore(null); return; } for (int i = 0; i < libri.length; i++) { if (libri[i] == null) { libri[i] = libro; return; } } System.out.println("Scaffale pieno!"); } public Libro[] getLibri() { return libri; } public void setGenere(String genere) { if (GenereUtils.isGenereValido(genere)) { this.genere = genere; } else { GenereUtils.stampaErrore(genere); } } public String getGenere() { return genere; } }
Notare che il metodosetGenere
, è praticamente lo stesso metodo definito per la classeLibro
, potendo sfruttare i metodi diGenereUtils
. Il metodoaggiungiLibro
invece, in primo luogo controlla se l'oggettoScaffale
corrente ha la variabilegenere
valorizzata. In caso negativo stampa un messaggio di errore e non esegue il resto del metodo. Poi invece cerca il primo posto libero nell'arraygeneri
, se non lo trova, allora stampa un messaggio di errore per avvertire che non c'è posto sullo scaffale per altri libri.
La classe singletonLibreria
infine, potrebbe essere la seguente:
public class Libreria { private String nome; private Scaffale[] scaffali; private static Libreria instance; private Libreria() { scaffali = new Scaffale[GenereUtils.generi.length]; } public static Libreria getInstance(){ if (instance == null) { instance = new Libreria(); } return instance; } public void setNome(String nome) { this.nome = nome; } public String getNome() { return nome; } public void aggiungiScaffale(Scaffale scaffale) { if (scaffali[scaffali.length-1] != null) { System.out.println("Questa libreria ha già tutti gli scaffali!"); return; } for (int i = 0; i < scaffali.length; i++) { if (scaffali[i] == null) { scaffali[i] = scaffale; break; } else if (scaffali[i].getGenere().equals(scaffale.getGenere())) { System.out.println("Scaffale genere " + scaffale.getGenere() + " già esistente!"); break; } } } public Scaffale[] getScaffali() { return scaffali; } }
Possiamo notare che all'interno di questa classe abbiamo utilizzato sempre la variabilelength
degli array coinvolti, per non essere soggetti a modifiche nel caso di variazioni del numero di generi utilizzati nel programma.
Il metodoaggiungiScaffale
invece, implementa un algoritmo che prima controlla se tutti gli scaffali sono già stati settati. Poi all'interno di un ciclo che itera sull'array scaffali, cerca il primo posto libero nell'array. Per ogni scaffale già posizionato all'interno dell'array, il metodocontrolla
se esso ha un genere coincidente con quello dello scaffale che è stato passato come parametro (in caso affermativo, stampa un messaggio di errore e termina il metodo con un comandoreturn
).
Possiamo testare il tutto con la seguente classe:
public class TestLibreria { public static void main(String[] args) { Libro jfaVol1 = new Libro("979-12-200-4915-3", "Java for Aliens Vol. 1", "Claudio De Sio Cesari", 25, GenereUtils.MANUALE); Libro jfaVol2 = new Libro("979-12-200-4916-0", "Java for Aliens Vol. 2", "Claudio De Sio Cesari", 25, GenereUtils.MANUALE); Libro f451 = new Libro("978-88-046-6529-8", "Fahrenheit 451", "Ray Bradbury", 10, GenereUtils.FANTASCIENZA); Libro shining = new Libro("978-88-452-9530-0", "Shining", "Stephen King", 12, GenereUtils.THRILLER); Libro icda = new Libro("978-88-683-6730-5", "Il cacciatore di aquiloni", "Khaled Hosseini", 11, GenereUtils.ROMANZO); Libro ltdt = new Libro("978-88-170-7976-1", "La teoria del tutto", "Stephen Hawking", 10, GenereUtils.SAGGIO); Scaffale scaffaleManuali = new Scaffale(GenereUtils.MANUALE); Scaffale scaffaleFantascienza = new Scaffale(GenereUtils.FANTASCIENZA); Scaffale scaffaleFantascienza2 = new Scaffale(GenereUtils.FANTASCIENZA); Scaffale scaffaleThriller = new Scaffale(GenereUtils.THRILLER); Scaffale scaffaleRomanzi = new Scaffale(GenereUtils.ROMANZO); Scaffale scaffaleSaggi = new Scaffale(GenereUtils.SAGGIO); Scaffale scaffaleSaggi2 = new Scaffale(GenereUtils.SAGGIO); scaffaleManuali.aggiungiLibro(jfaVol1); scaffaleManuali.aggiungiLibro(jfaVol2); scaffaleFantascienza.aggiungiLibro(f451); scaffaleThriller.aggiungiLibro(shining); scaffaleRomanzi.aggiungiLibro(icda); scaffaleSaggi.aggiungiLibro(ltdt); Libreria libreria = Libreria.getInstance("Libreria per alieni"); libreria.aggiungiScaffale(scaffaleManuali); libreria.aggiungiScaffale(scaffaleFantascienza); libreria.aggiungiScaffale(scaffaleFantascienza2); libreria.aggiungiScaffale(scaffaleThriller); libreria.aggiungiScaffale(scaffaleRomanzi); libreria.aggiungiScaffale(scaffaleSaggi); libreria.aggiungiScaffale(scaffaleSaggi2); Scaffale[] scaffali = libreria.getScaffali(); System.out.println("Elenco scaffali della libreria:"); for (Scaffale scaffale : scaffali) { System.out.println("Scaffale " + scaffale.getGenere() + ":"); Libro[] libri = scaffale.getLibri(); for (Libro libro : libri) { if (libro != null) { System.out.println("\t" + libro.getTitolo() + " di " + libro.getAutore() + " (Genere " + libro.getGenere() + ")"); } } } } }
il cui output sarà:
Scaffale genere Fantascienza già esistente! Questa libreria ha già tutti gli scaffali! Elenco scaffali della libreria: Scaffale Manuale: Java for Aliens Vol. 1 di Claudio De Sio Cesari (Genere Manuale) Java for Aliens Vol. 2 di Claudio De Sio Cesari (Genere Manuale) Scaffale Fantascienza: Fahrenheit 451 di Ray Bradbury (Genere Fantascienza) Scaffale Thriller: Shining di Stephen King (Genere Thriller) Scaffale Romanzo: Il cacciatore di aquiloni di Khaled Hosseini (Genere Romanzo) Scaffale Saggio: La teoria del tutto di Stephen Hawking (Genere Saggio)
-
Esercizio 5.hh)
Quali delle seguenti affermazioni sono vere riguardo i diagrammi dei casi d'uso UML?
1. Nel diagramma dei casi d'uso UML non è possibile utilizzare il simbolo di classe.
2. Un caso d'uso è rappresentato con un rettangolo in UML.
3. Il diagramma dei casi d'uso è un diagramma statico, infatti non contiene informazioni riguardo la linea temporale.
4. Gli attori del diagramma dei casi d'uso rappresentano gli utenti del sistema, e quindi persone fisiche.
5. Un caso d'uso può essere collegato a più attori.
Soluzione
Le uniche risposte false sono la numero 2 e la numero 4. Infatti, per quanto riguarda l'affermazione numero 2, l'unico rettangolo che fa parte della sintassi del diagramma dei casi d'uso, è il system boundary, che circonda solitamente i casi d'uso e delimita il sistema. Per quanto riguarda l'affermazione numero 4, in realtà il concetto di attore, va interpretato come "ruolo". Un utente del sistema potrebbe essere anche un altro sistema.
-
Esercizio 5.ii)
Quali delle seguenti affermazioni sono vere?
1. Uno scenario di un caso d'uso non contiene condizioni.
2. È possibile descrivere gli scenari con un activity diagram oltre che con del testo semplice.
3. Bisognerebbe descrivere per ogni caso d'uso tutti gli scenari.
4. La descrizione degli scenari ci permette di scoprire eventuali flussi, prima di ancora di scrivere il codice.
5. Un deployment diagram mostra l'interazione tra l'utente e il sistema.
6. Le classi candidate sono le classi fondamentali che sicuramente saranno implementate nel nostro programma.
Soluzione
Solo le ultime due affermazioni sono false. In particolare la definizione riportata nell'affermazione numero 5 è quella del diagramma dei casi d'uso, non del deployment diagram. Invece la definizione riportata nell'affermazione numero 6 è quella di astrazioni chiave (key abstractions), non di classi candidate.
-
Esercizio 5.jj)
Quali delle seguenti affermazioni sono vere riguardo il sequence diagram?
1. Un activity diagram ci permette di simulare uno scenario, ma usando oggetti.
2. L'activity line (linea di attività) di un oggetto è rappresentata da una linea tratteggiata.
3. I messaggi definiscono i metodi.
4. Nell'activity diagram, è possibile rappresentare un attore.
5. Il nome degli oggetti in un sequence diagram sono rappresentati con la sintassi nomeOggetto:NomeClasse, ma sia il nome dell'oggetto, che il nome della classe possono essere omessi.
Soluzione
Solo l'affermazione 2 è errata, in quanto riporta la definizione di linea di vita di un oggetto, e non di linea di attività.