Cominciamo questa lezione con un esercizio, che ci condurrà ad un nuovo design pattern.
Per un programma di astronomia, si implementino le tre classi Planet, Moon e Star, che rappresentano, rispettivamente, un pianeta, una luna (ovvero, un satellite naturale) ed una stella.
A ciascuno di questi corpi celesti è associato un nome ed una massa.
Quando si definisce un pianeta o una luna, bisogna anche specificare il corpo intorno al quale orbitano.
Le classi devono verificare che i pianeti orbitino intorno a una stella, e le lune intorno a dei pianeti.
Il tentativo di violare queste regole porta ad un’eccezione.
Si fornisca anche una classe (o interfaccia) Body che rappresenta un generico corpo celeste, ed è quindi una superclasse di tutte le precedenti.
La classe Body dispone di un metodo satelliteIterator, che restituisce un iteratore sull’insieme di satelliti di questo corpo.
La slide seguente mostra un esempio d’uso di queste classi.
Le classi descritte nella slide precedente devono poter essere utilizzate nel modo seguente i dati astronomici sono tratti da www.wikipedia.org
Body b[] = new Body[5];
b[0] = new Star("Sole", 1.98e30);
// Marte orbita intorno al sole
b[1] = new Planet("Marte", 6.14e23, b[0]);
// Phobos e Deimos orbitano intorno a Marte
b[2] = new Moon("Phobos", 1.07e16, b[1]);
b[3] = new Moon("Deimos", 2.24e15, b[1]);
java.util.Iterator i = b[1].satelliteIterator();
System.out.println(" Lune di Marte: ");
while (i.hasNext()) {
...Moon m = (Moon) i.next();
...System.out.println(m.getName() + " : " + m.getMass());
}
System.out.println(" ed ora...");
b[4] = new Moon("Giove", 1, b[0]);
Il codice della slide precedente dovrebbe avere un output simile al seguente:
Lune di Marte:
Phobos : 1.07E16
Deimos : 2.24E15
ed ora...
Exception in thread "main" planets.Body$InvalidBodyTypeException
at planets.Body.addSatellite(Body.java:32)
at planets.Body.(Body.java:17)
at planets.Moon.(Moon.java:6)
at PianetiTest.main(PianetiTest.java:24)
Ovvero, l’iteratore restituito dal metodo satelliteIterator permette di ottenere, una alla volta, le due lune di Marte.
L’ultima istruzione, che tenta di definire Giove come una luna del sole, fallisce lanciando un’eccezione, perché una luna non può orbitare direttamente intorno ad una stella.
Abbozziamo una soluzione dell’esercizio, con particolare riferimento al controllo necessario ad assicurare che lune e pianeti orbitino intorno a corpi del tipo giusto.
Cercheremo di seguire il principio secondo il quale bisogna raccogliere nelle superclassi (in questo caso, Body) il maggior numero possibile di funzionalità comuni.
Decidiamo quindi di introdurre in Body un metodo di supporto, chiamato isValidSatellite, che prende un corpo celeste x come argomento, e restituisce vero se e solo se questo corpo potrebbe orbitare intorno ad x.
Body sarà una classe astratta perché non ha senso in questo contesto istanziare un corpo celeste generico.
Questo ci dà l’occasione di lasciare il metodo isValidSatellite astratto, in modo che ciascuna sottoclasse lo ridefinisca nel modo appropriato.
Le prossime slide presentano una possibile implementazione della classe Body.
Riportiamo una possibile implementazione della classe Planet.
Grazie ad una accurata pianificazione delle responsabilità, siamo riusciti a ridurre al minimo il codice contenuto nelle sottoclassi come Planet.
L’annotazione “@Override” indica che il metodo che segue intende eseguire l’overriding di uno della superclasse, le annotazioni verranno illustrate in una lezione successiva.
Contesto:
Soluzione:
Il diagramma a destra illustra graficamente le classi coinvolte nel pattern TEMPLATE METHOD.
Vi si trova la superclasse, con metodi primitivi e metodo composito “algorithm”, e una sottoclasse, che ridefinisce appropriatamente i metodi primitivi.
Con riferimento all’esercizio dei corpi celesti:
Contesto:
Soluzione:
Il diagramma a destra illustra i rapporti tra le classi coinvolte nel pattern FACTORY METHOD.
Le due interfacce Creator e Product rappresentano il creatore generico e il prodotto generico, rispettivamente.
La relazione di dipendenza tra Creator e Product è dovuta semplicemente al fatto che il creatore ha un metodo (il metodo fabbrica, per l’appunto) che restituisce un oggetto di tipo Product.
Al di sotto, troviamo un creatore concreto e un prodotto concreto.
Naturalmente, la relazione di dipendenza si estende anche a loro.
Il diagramma a destra illustra come il pattern FACTORY METHOD si ritrova applicato nella creazione di iteratori da parte delle collezioni come LinkedList.
Come si vede, Iterable rappresenta il creatore generico e Iterator il prodotto generico.
Un produttore generico è rappresentato dalla classe LinkedList, che implementa Iterable.
La classe LinkedList costruisce internamente un iteratore concreto, di una classe che non è documentata nella libreria standard, e che nel diagramma viene chiamata ConcreteIterator, è possibile che in realtà sia una classe interna privata di LinkedList.
È utile considerare esempi di progettazione che assomigliano al pattern FACTORY METHOD, ma che in realtà non rappresentano.
Ad esempio, consideriamo il metodo toString della classe Object:
Esercizio: esaminare la documentazione del metodo createEtchedBorder della classe javax.swing.BorderFactory e stabilire se si tratta di un esempio del pattern FACTORY METHOD.
4. Risoluzione dell'overloading e dell'overriding
5. Controllo di uguaglianza tra oggetti
6. Classi interne, locali ed anonime
7. Iteratori, teoria e pratica
8. Clonazione di oggetti. Confronto tra oggetti.
9. Elementi di programmazione di interfacce grafiche
10. Il paradigma Model-View-Controller. Il pattern Strategy
11. I pattern Composite e Decorator
12. I pattern Template Method e Factory Method
13. Classi e metodi parametrici
14. La libreria Java Collection Framework: le interfacce Iterable, ...
15. La libreria Java Collection Framework: la classe HashSet e le l...
16. Parametri di tipo con limiti
17. L'implementazione della programmazione generica: la cancellazio...
18. La riflessione
19. Introduzione al multi-threading
22. Classi enumerate