Principi GRASP per l'assegnazione delle responsabilità
In questa pagina 6
Definizione (GRASP). General Responsibility Assignment Software Patterns: nove principi, espressi come pattern (problema-soluzione con un nome), per assegnare le responsabilità alle classi e progettare le interazioni tra oggetti. Il nome suggerisce l'importanza di afferrare (to grasp) i principi fondamentali della progettazione a oggetti. Il corso tratta i cinque principali: Creator, Information Expert, Low Coupling, Controller, High Cohesion.
È il passo che sta prima dei pattern GoF (Design pattern - introduzione e catalogo GoFUn design pattern è una soluzione generale, riusabile e collaudata a un problema ricorrente di progettazione in un certo contesto, con un nome (che arricchisce il vocabolario dei progettisti), un problema, una soluzione (struttura e partecipanti) e le conseguenze; il catalogo GoF (Gamma, Helm, Johnson, Vlissides) ha 23 pattern in tre famiglie: 5 creazionali (come si creano gli oggetti), 7 strutturali (come si compongono classi e oggetti), 11 comportamentali (come collaborano e si ripartiscono le responsabilità); dietro i pattern ci sono principi come programmare verso un'interfaccia, incapsulare ciò che varia, preferire la composizione all'ereditarietà, aperto/chiuso e inversione delle dipendenze; un pattern si sceglie dal problema, non dal nome, e usarlo senza bisogno complica il progetto.Design pattern - introduzione e catalogo GoF →): dopo i requisiti (Ingegneria dei requisiti - requisiti funzionali e non funzionaliUn requisito è la descrizione di un servizio o di un vincolo del sistema, scritta ad astrazione diversa per chi decide (requisiti utente, in linguaggio naturale) e per chi realizza (requisiti di sistema, strutturati e dettagliati); si dividono in funzionali (servizi e comportamento), non funzionali (proprietà e vincoli: prodotto, organizzativi, esterni, da scrivere con una misura verificabile) e di dominio; il processo di ingegneria dei requisiti è iterativo (elicitazione con interviste, etnografia, storie e scenari; analisi e negoziazione; specifica; validazione; gestione del cambiamento con identificatori e tracciabilità) e produce il documento dei requisiti, che dice cosa fa il sistema e non come; i modelli UML (contesto, casi d'uso, sequenza, classi, stati) aiutano a chiarire i requisiti.Ingegneria dei requisiti - requisiti funzionali e non funzionali →) e un modello del dominio, non basta dire "aggiungi metodi alle classi": bisogna decidere quale classe fa che cosa. La progettazione guidata dalle responsabilità (responsibility-driven design) lo fa in modo sistematico. Tutti i programmi della nota sono compilati ed eseguiti con JDK 21.
Responsabilità
Definizione. Una responsabilità è un contratto o obbligo di una classe. Si distinguono:
- di sapere (knowing): che cosa la classe deve conoscere (i suoi dati privati, gli oggetti collegati, ciò che può calcolare); derivano dagli attributi e dalle associazioni del modello di dominio. Esempio: un
Prestitoè responsabile di sapere la propria scadenza.- di fare (doing): che cosa deve fare (creare un oggetto, un calcolo), avviare azioni in altri oggetti, controllare e coordinare azioni di altri. Esempio: una
Bibliotecaè responsabile di creare iPrestito.
Le responsabilità si realizzano con metodi e con le collaborazioni tra oggetti: un metodo può fare tutto da solo o collaborare con altri (Sale.getTotal() somma i getSubtotal() delle righe: la responsabilità "conoscere il totale" è del Sale, e usa i sottototali delle SalesLineItem). Un messaggio come makePayment inviato a un Sale esprime la responsabilità del Sale di creare il Payment. Gli ingredienti di partenza della progettazione sono: testo dei casi d'uso, diagramma di sequenza di sistema, contratti delle operazioni, specifica supplementare, glossario, modello di dominio.
I cinque principi
| principio | problema | soluzione |
|---|---|---|
| Creator | chi crea un'istanza di A? | assegna alla classe B la creazione di A se (più condizioni valgono, meglio è): B contiene o aggrega A, registra A, usa strettamente A, ha i dati per inizializzare A |
| Information Expert | come assegnare una responsabilità? | alla classe che ha le informazioni per soddisfarla |
| Low Coupling | come ridurre l'impatto dei cambiamenti e favorire il riuso? | assegna le responsabilità in modo che l'accoppiamento (collegamento tra classi) resti basso |
| Controller | chi riceve e gestisce un evento di sistema (dall'interfaccia)? | un oggetto che rappresenta il sistema (stile Facade) o uno scenario di caso d'uso (session controller) |
| High Cohesion | come mantenere le classi focalizzate e gestibili? | assegna le responsabilità in modo che la coesione resti alta |
Creator
Si usa questa tabella delle condizioni per scegliere:
| B ... A | esempio |
|---|---|
| contiene o aggrega A (in una collezione) | il Tabellone contiene le Casella e le crea |
| registra A | la Biblioteca registra i Prestito |
| usa strettamente A | un Prestito crea la sua Ricevuta |
| ha i dati di inizializzazione di A | chi conosce tessera e documento crea il Prestito |
Si connette un oggetto al suo creatore così l'accoppiamento non cresce (B già conosce A). Cautele: quando la creazione è complessa (riuso di istanze per le prestazioni, creazione condizionata da una famiglia di classi simili) si usano le fabbriche (Pattern creazionali - Singleton, Factory Method, Abstract Factory, Builder e PrototypeI pattern creazionali isolano la creazione degli oggetti dal codice che li usa: Singleton garantisce un'unica istanza (costruttore privato, metodo statico getInstance; la versione lazy ingenua non è thread-safe, si correggono con synchronized, inizializzazione eager, double-checked locking con volatile, holder idiom o enum); Factory Method lascia alle sottoclassi la scelta della classe da istanziare; Abstract Factory crea famiglie coerenti di prodotti correlati tramite un'interfaccia di fabbrica; Builder costruisce passo passo oggetti complessi con campi facoltativi; Prototype crea oggetti clonando un prototipo (copia profonda delle parti mutabili); la Simple Factory non è un pattern GoF ma sposta il codice di creazione in una classe sola.Pattern creazionali - Singleton, Factory Method, Abstract Factory, Builder e Prototype →).
Information Expert
Chi ha le informazioni, fa il lavoro. L'oggetto che conosce data e durata sa calcolare la scadenza; quello che conosce la lista delle caselle sa trovare la casella successiva (nell'esempio delle slide: perché la ricerca getSquare sta nel tabellone e non altrove?). Vantaggi: incapsulamento (le classi usano i propri dati) e classi leggere e coese. Controindicazioni: l'Expert può contraddire Low Coupling e High Cohesion (non si mette nel Prestito il codice per scrivere sul database, anche se "ha i dati": si separano i livelli di business, interfaccia e persistenza, vedi Architettura del software - decisioni, organizzazione e stiliL'architettura del software descrive come un sistema è organizzato in componenti, le loro relazioni e la loro distribuzione; è il ponte tra requisiti e progetto dettagliato, e le sue decisioni derivano soprattutto dai requisiti non funzionali (prestazioni, sicurezza, disponibilità, manutenibilità), spesso in conflitto tra loro; si descrive con più viste (logica, processi, sviluppo, fisica); gli stili di organizzazione ricorrenti sono a livelli (layered), MVC, repository, client-server, pipe and filter, publish-subscribe e microservizi; la modularizzazione mira ad alta coesione e basso accoppiamento, nascondendo le decisioni dietro interfacce (information hiding, inversione delle dipendenze).Architettura del software - decisioni, organizzazione e stili →).
Low Coupling
Definizione (accoppiamento). Misura di quanto una classe dipende da altre (ha attributi di quel tipo, chiama i loro metodi, ne conosce la struttura, estende o implementa). L'accoppiamento alto significa che un cambiamento in una classe si propaga alle altre.
Una classe a basso accoppiamento è comprensibile da sola, manutenibile (non risente dei cambiamenti altrui), riusabile. Nell'esempio di Monopoli delle slide la Pedina conosce solo il Tabellone, non la lista di Casella: se la lista diventasse un grafo, la Pedina non cambierebbe. Non ci si preoccupa dell'accoppiamento con classi stabili di librerie o molto usate e testate (per esempio java.util.List).
Controller
Il controller è il primo oggetto oltre l'interfaccia a ricevere un'operazione di sistema (premere "Presta", un messaggio REST) e a coordinare la risposta, delegando il lavoro agli oggetti di dominio: non contiene la logica. Due scelte: un controller di sistema/di facciata (un oggetto per tutto il sistema, ricordando il pattern Facade, Pattern strutturali - Adapter, Decorator, Composite, Facade e ProxyI pattern strutturali compongono classi e oggetti in strutture più grandi: Adapter converte l'interfaccia di una classe in quella attesa dal client (di oggetto, per composizione, o di classe, per ereditarietà multipla di classe e interfaccia); Decorator aggiunge responsabilità a un oggetto con un involucro che ha la stessa interfaccia (l'ordine dei decoratori conta); Composite tratta in modo uniforme foglie e contenitori in una struttura ad albero; Facade offre un'interfaccia semplice a un sottosistema (non cerca il polimorfismo, a differenza dell'Adapter); Proxy è un sostituto con la stessa interfaccia che controlla l'accesso (virtuale, di protezione, di cache, dinamico); Bridge e Flyweight completano le sette forme del catalogo.Pattern strutturali - Adapter, Decorator, Composite, Facade e Proxy →) o un controller di caso d'uso (uno per scenario). Il rischio è il controller gonfio (bloated) che fa tutto; si ripara delegando. Nel pattern MVC è il C (JSP e pattern Model-View-ControllerScrivere l'HTML con println dentro la servlet mescola logica e presentazione. Una JSP (Jakarta Pages) è una pagina HTML con parti dinamiche che Jasper traduce in una servlet alla prima richiesta (quindi vale la stessa concorrenza). Si usano la direttiva page, le espressioni EL (Expression Language) per leggere attributi e proprietà dei JavaBean (la proprietà nome chiama getNome), gli oggetti impliciti (param, requestScope, sessionScope, pageContext) e le librerie JSTL core (c:out, c:if, c:choose, c:forEach); l'EL non fa escape, quindi i dati dell'utente passano da c:out. Il pattern Model-View-Controller separa il model (JavaBean e DAO: dati e regole), la view (JSP, che mostra) e il controller (servlet: legge la richiesta, chiama il model, mette i risultati negli attributi della richiesta e fa il forward alla view). Le JSP stanno in WEB-INF così non si raggiungono direttamente. Forward: stessa richiesta, stesso URL, attributi condivisi, lato server; redirect: risposta 302 con Location, nuova richiesta GET, URL cambiato, attributi persi. Il pattern Post/Redirect/Get risponde alla POST con un redirect a una GET, per evitare che il ricaricamento rinvii il form; il messaggio di conferma passa in sessione (flash) e si mostra una volta sola; in caso di errore invece si fa il forward alla stessa pagina con il form compilato e lo stato 4xx.JSP e pattern Model-View-Controller →).
High Cohesion
Definizione (coesione). Misura di quanto le responsabilità di una classe sono fortemente collegate e focalizzate su un unico scopo.
Una classe a bassa coesione fa molte cose scollegate (un "dio" come Sistema che calcola scadenze, controlla limiti, scrive su file): è difficile da capire, riusare e mantenere. L'alta coesione complementa il basso accoppiamento. Cautela: a volte si accettano oggetti meno coesi che offrono un'interfaccia per molte operazioni quando c'è un costo di comunicazione remota (oggetti remoti: meno chiamate); non si raggruppano responsabilità solo perché le mantiene una persona sola.
Un esempio completo: dal "dio" alla distribuzione delle responsabilità
Requisito. Registrare il prestito di un documento a un utente se l'utente non ha raggiunto il suo limite e il documento ha una copia; la scadenza è 30 giorni dopo (3 per i DVD).
Progetto 1: una classe che sa tutto. Sistema tiene le mappe di utenti e copie, e il suo metodo registraPrestito controlla il limite, decrementa le copie, calcola la scadenza e registra il prestito come un array di String. Le altre classi sono contenitori di dati. Problemi: bassa coesione (un'unica classe con tutte le regole), alto accoppiamento (tutto dipende da Sistema e dalla rappresentazione con array di stringhe), nessun oggetto usa i propri dati (viola Information Expert).
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** PRIMA: una classe "dio" che sa tutto e fa tutto; le altre sono sacchi di dati. */
class Dati {
String idUtente; int limite; List<String> titoliInPrestito = new ArrayList<>();
}
class Sistema {
Map<String, Dati> utenti = new HashMap<>();
Map<String, Integer> copie = new HashMap<>();
List<String[]> prestiti = new ArrayList<>(); // {utente, titolo, scadenza} come array di String
boolean registraPrestito(String idUtente, String titolo) {
Dati u = utenti.get(idUtente);
if (u.titoliInPrestito.size() >= u.limite) return false; // la regola del limite vive qui
if (copie.get(titolo) <= 0) return false; // e quella delle copie
copie.put(titolo, copie.get(titolo) - 1);
u.titoliInPrestito.add(titolo);
String scadenza = LocalDate.of(2026, 10, 14).plusDays(titolo.startsWith("DVD") ? 3 : 30).toString();
prestiti.add(new String[]{idUtente, titolo, scadenza}); // e il calcolo della scadenza
return true;
}
}
public class Prima {
public static void main(String[] args) {
Sistema s = new Sistema();
Dati anna = new Dati(); anna.idUtente = "anna"; anna.limite = 1;
s.utenti.put("anna", anna);
s.copie.put("rosa", 2);
System.out.println(s.registraPrestito("anna", "rosa"));
System.out.println(s.registraPrestito("anna", "rosa"));
System.out.println(s.prestiti.get(0)[2]);
}
}true
false
2026-11-13Progetto 2: responsabilità distribuite.
| responsabilità | assegnata a | principio |
|---|---|---|
| sapere se il documento ha copie, prelevarne una | Documento |
Information Expert |
| sapere se l'utente può prendere altri documenti | Utente |
Information Expert (ha limite e prestiti attivi) |
| calcolare la scadenza | Prestito |
Information Expert (ha inizio e documento, che ha la durata) |
creare i Prestito |
Biblioteca |
Creator (li registra) |
ricevere l'operazione registraPrestito e coordinare |
Biblioteca (controller di caso d'uso, qui anche di sistema) |
Controller |
| non conoscere le classi interne | il client parla solo con Biblioteca |
Low Coupling |
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/** Information Expert: il documento conosce copie e durata del prestito, quindi decide lui. */
class Documento {
private final String titolo;
private final int giorniPrestito;
private int copie;
Documento(String titolo, int copie, int giorniPrestito) {
this.titolo = titolo; this.copie = copie; this.giorniPrestito = giorniPrestito;
}
String titolo() { return titolo; }
int giorniPrestito() { return giorniPrestito; }
boolean haCopie() { return copie > 0; }
void prelevaCopia() {
if (copie == 0) throw new IllegalStateException("nessuna copia di " + titolo);
copie--;
}
}
/** Information Expert: il prestito conosce data e durata, quindi calcola la scadenza. */
class Prestito {
private final Utente utente;
private final Documento documento;
private final LocalDate inizio;
Prestito(Utente utente, Documento documento, LocalDate inizio) {
this.utente = utente; this.documento = documento; this.inizio = inizio;
}
LocalDate scadenza() { return inizio.plusDays(documento.giorniPrestito()); }
}
/** Information Expert: l'utente conosce limite e prestiti attivi. */
class Utente {
private final int limite;
private final List<Prestito> attivi = new ArrayList<>();
Utente(int limite) { this.limite = limite; }
boolean puoPrendereAltri() { return attivi.size() < limite; }
void aggiungi(Prestito p) { attivi.add(p); }
}
/** Controller (di caso d'uso, che coordina) e Creator di Prestito: registra i prestiti e ne ha i dati. */
class Biblioteca {
private final Map<String, Utente> utenti = new HashMap<>();
private final Map<String, Documento> catalogo = new HashMap<>();
private final List<Prestito> prestiti = new ArrayList<>();
void aggiungi(String id, Utente u) { utenti.put(id, u); }
void aggiungi(Documento d) { catalogo.put(d.titolo(), d); }
/** Operazione di sistema: ogni passo e' delegato all'oggetto che ha le informazioni. */
boolean registraPrestito(String idUtente, String titolo, LocalDate oggi) {
Utente u = utenti.get(idUtente);
Documento d = catalogo.get(titolo);
if (!u.puoPrendereAltri() || !d.haCopie()) return false;
d.prelevaCopia();
Prestito p = new Prestito(u, d, oggi); // Creator: Biblioteca registra i Prestito
u.aggiungi(p);
prestiti.add(p);
System.out.println("scadenza: " + p.scadenza());
return true;
}
}
public class Dopo {
public static void main(String[] args) {
Biblioteca b = new Biblioteca();
b.aggiungi("anna", new Utente(1));
b.aggiungi(new Documento("rosa", 2, 30));
LocalDate oggi = LocalDate.of(2026, 10, 14);
System.out.println(b.registraPrestito("anna", "rosa", oggi));
System.out.println(b.registraPrestito("anna", "rosa", oggi));
}
}scadenza: 2026-11-13
true
falseIl comportamento è identico (stessa scadenza, stesse risposte true e false) ma ora ogni regola vive dove stanno i dati e si può cambiare (per esempio la durata dei prestiti) toccando una classe sola.
L'esempio delle slide: il tabellone
Le slide di Larman usano un gioco da tavolo: chi crea le caselle? (il Tabellone, che le contiene: Creator); chi trova la casella a un certo spostamento? (il Tabellone, che conosce la lista: Expert, e così la Pedina non dipende dalla lista: Low Coupling); chi riceve l'evento "gioca un turno"? (un Gioco, che rappresenta il sistema o lo scenario: Controller). Programma completo:
import java.util.ArrayList;
import java.util.List;
/** L'esempio delle slide (gioco da tavolo): chi crea le caselle, chi ne sa trovare una, chi riceve l'evento. */
class Casella {
private final String nome;
Casella(String nome) { this.nome = nome; }
String nome() { return nome; }
}
/** Creator: la Board contiene (aggrega) le Casella e ha i dati per crearle. */
class Tabellone {
private final List<Casella> caselle = new ArrayList<>();
Tabellone(String... nomi) {
for (String n : nomi) caselle.add(new Casella(n)); // GRASP Creator
}
/** Information Expert: solo il tabellone conosce la lista, quindi e' lui a trovare la casella. */
Casella getCasella(Casella partenza, int spostamento) {
int i = caselle.indexOf(partenza);
return caselle.get((i + spostamento) % caselle.size()); // il giro del tabellone e' un suo dettaglio
}
Casella prima() { return caselle.get(0); }
}
class Pedina {
private Casella posizione;
private final Tabellone tabellone;
Pedina(Tabellone t) { this.tabellone = t; this.posizione = t.prima(); }
/** Pedina conosce solo Tabellone (non la lista di caselle): basso accoppiamento. */
void muovi(int dadi) { posizione = tabellone.getCasella(posizione, dadi); }
Casella posizione() { return posizione; }
}
/** Controller: riceve l'evento di sistema "gioca un turno" e lo smista, senza fare i calcoli lui. */
class Gioco {
private final Pedina pedina;
Gioco(Pedina p) { this.pedina = p; }
void giocaTurno(int d1, int d2) {
pedina.muovi(d1 + d2);
System.out.println("la pedina e' su " + pedina.posizione().nome());
}
}
public class Monopoli {
public static void main(String[] args) {
Tabellone t = new Tabellone("Via", "Vicolo Corto", "Vicolo Stretto", "Stazione", "Largo Colombo");
Gioco g = new Gioco(new Pedina(t));
g.giocaTurno(1, 2); // Via + 3 = Stazione
g.giocaTurno(4, 4); // Stazione + 8 -> (3 + 8) % 5 = 1 = Vicolo Corto
}
}la pedina e' su Stazione
la pedina e' su Vicolo CortoAltri quattro principi GRASP
| principio | problema | soluzione | pattern GoF collegato |
|---|---|---|---|
| Polymorphism | come gestire alternative che dipendono dal tipo? | assegna la responsabilità ai tipi (classi) con operazioni polimorfiche, senza if sul tipo |
Strategy, State |
| Pure Fabrication | che cosa fare se rispettare Expert, coesione e accoppiamento porta a un progetto pessimo? | inventa una classe che non è nel dominio (per esempio PrestitoRepository, che salva i prestiti) |
Adapter, molte "fabbriche" |
| Indirection | come disaccoppiare due oggetti? | un oggetto intermedio che li media | Adapter, Facade, Proxy, Observer |
| Protected Variations | come progettare perché le variazioni e le instabilità non alterino gli altri elementi? | un'interfaccia stabile attorno al punto di variazione | Factory, Strategy, adattatori |
Questi principi e l'inversione delle dipendenze (programmare verso un'interfaccia, vedi Design pattern - introduzione e catalogo GoFUn design pattern è una soluzione generale, riusabile e collaudata a un problema ricorrente di progettazione in un certo contesto, con un nome (che arricchisce il vocabolario dei progettisti), un problema, una soluzione (struttura e partecipanti) e le conseguenze; il catalogo GoF (Gamma, Helm, Johnson, Vlissides) ha 23 pattern in tre famiglie: 5 creazionali (come si creano gli oggetti), 7 strutturali (come si compongono classi e oggetti), 11 comportamentali (come collaborano e si ripartiscono le responsabilità); dietro i pattern ci sono principi come programmare verso un'interfaccia, incapsulare ciò che varia, preferire la composizione all'ereditarietà, aperto/chiuso e inversione delle dipendenze; un pattern si sceglie dal problema, non dal nome, e usarlo senza bisogno complica il progetto.Design pattern - introduzione e catalogo GoF →) sono la ragione per cui i pattern GoF funzionano: ogni pattern GoF si può leggere come applicazione di uno o più principi GRASP.
Come si ragiona su un esercizio
- Elencare le operazioni di sistema dal caso d'uso e dai requisiti (
registraPrestito,registraRestituzione). - Per ognuna, tracciare la sequenza di messaggi e decidere per ogni passo chi lo esegue:
- la creazione? Creator;
- la decisione o il calcolo? chi ha le informazioni (Expert);
- il coordinamento dall'interfaccia? Controller.
- Controllare accoppiamento (quante classi conosce ognuna) e coesione (le responsabilità sono affini?); se violati, spostare o introdurre una classe (Pure Fabrication, Indirection).
- Giustificare ogni assegnazione con il principio, e dire quali alternative si scartano.
Un esercizio completo con codice: Esercizio - assegnare le responsabilità con GRASP alla restituzione di un prestito (progetto originale).
Domande d'esame
1. Che cos'è una responsabilità? Distinguere "fare" e "sapere" con un esempio.
Traccia. Contratto o obbligo di una classe, realizzato con metodi (da soli o in collaborazione). Sapere: legata agli attributi/associazioni (un Sale sa il proprio totale). Fare: creare oggetti, calcolare, avviare o coordinare azioni (il Sale crea i Payment).
2. Spiegare Information Expert e Low Coupling e dire in quali casi possono essere in conflitto.
Traccia. Expert: la responsabilità va alla classe che ha le informazioni, così si usano i propri dati (incapsulamento). Low Coupling: poche dipendenze tra classi. Conflitto: assegnare a Prestito la scrittura nel database (ha i dati) lo accoppia al database e ne riduce la coesione; si introduce una classe di persistenza (Pure Fabrication) e si separano i livelli.
3. Chi dovrebbe ricevere l'evento "registra restituzione" e perché non la classe Documento?
Traccia. Un Controller (di sistema o di caso d'uso, per esempio Biblioteca): coordina e delega a Prestito (calcolo del ritardo), Utente (multa), Documento (rimessa in scaffale e prenotazioni). Al Documento non va perché non rappresenta il sistema né lo scenario e avrebbe bassa coesione (conosce utenti, multe, prestiti).
Versione ripasso
- GRASP: General Responsibility Assignment Software Patterns; principi per assegnare le responsabilità alle classi; vengono prima dei pattern GoF.
- Responsabilità: contratto di una classe. Sapere (dati, oggetti collegati, calcoli) e fare (creare, calcolare, avviare azioni in altri oggetti, coordinare). Si realizzano con metodi e collaborazioni (
Sale.getTotal()somma igetSubtotal();makePaymentilSalecrea ilPayment).
| principio | problema | soluzione |
|---|---|---|
| Creator | chi crea A? | B se contiene/aggrega, registra, usa strettamente A o ha i dati per inizializzarla |
| Information Expert | a chi la responsabilità? | a chi ha le informazioni |
| Low Coupling | come ridurre l'impatto dei cambiamenti? | poche dipendenze tra classi |
| Controller | chi riceve l'evento di sistema? | un oggetto che rappresenta il sistema (Facade) o il caso d'uso; coordina, non calcola |
| High Cohesion | come tenere classi focalizzate? | responsabilità affini in ogni classe |
- Cautele: Creator con creazione complessa fabbriche; Expert contro Low Coupling e High Cohesion (non scrivere sul database nel
Prestito: separare i livelli); non preoccuparsi dell'accoppiamento con classi stabili di libreria; controller gonfio delegare; oggetti meno coesi accettabili per ridurre le chiamate remote. - Dal "dio" alla distribuzione:
Sistemache controlla limite, copie, scadenza e registra array diString= bassa coesione e alto accoppiamento. Distribuzione:Documento(copie),Utente(limite, prestiti attivi),Prestito(scadenza),Biblioteca(Creator deiPrestitoe ControllerregistraPrestito).
boolean registraPrestito(String idUtente, String titolo, LocalDate oggi) { // Controller: coordina e delega
Utente u = utenti.get(idUtente);
Documento d = catalogo.get(titolo);
if (!u.puoPrendereAltri() || !d.haCopie()) return false; // gli Expert decidono
d.prelevaCopia();
Prestito p = new Prestito(u, d, oggi); // Creator: registra i Prestito
u.aggiungi(p);
prestiti.add(p);
return true;
} Uscite: scadenza: 2026-11-13, true, false (limite 1), identiche al progetto del "dio".
class Documento { // Information Expert: copie e durata sono suoi
private int copie;
private final int giorniPrestito;
boolean haCopie() { return copie > 0; }
void prelevaCopia() { if (copie == 0) throw new IllegalStateException("nessuna copia"); copie--; }
}
class Prestito { // Information Expert: ha inizio e documento
LocalDate scadenza() { return inizio.plusDays(documento.giorniPrestito()); }
}
class Utente { // Information Expert: limite e prestiti attivi
boolean puoPrendereAltri() { return attivi.size() < limite; }
}class Sistema { // PRIMA: tutto qui, le altre sono sacchi di dati
boolean registraPrestito(String idUtente, String titolo) {
Dati u = utenti.get(idUtente);
if (u.titoliInPrestito.size() >= u.limite) return false; // regola del limite
if (copie.get(titolo) <= 0) return false; // regola delle copie
copie.put(titolo, copie.get(titolo) - 1);
String scadenza = LocalDate.of(2026, 10, 14).plusDays(titolo.startsWith("DVD") ? 3 : 30).toString();
prestiti.add(new String[]{idUtente, titolo, scadenza}); // calcolo e registrazione come array di String
return true;
}
}- Quattro domande per ogni assegnazione: chi ha le informazioni? chi crea? chi coordina? quante classi deve conoscere (accoppiamento)? le sue responsabilità sono affini (coesione)?
- GRASP e GoF: Controller Facade; Indirection Adapter, Proxy; Polymorphism Strategy, State; Protected Variations Factory, interfacce stabili; Pure Fabrication repository e simili.
- Tabellone (slide):
Tabellonecrea leCasella(Creator) e trova la casella a uno spostamento (Expert);Pedinaconosce solo ilTabellone(Low Coupling);GiocoricevegiocaTurno(Controller). Uscite:la pedina e' su Stazione, poila pedina e' su Vicolo Corto. - Altri quattro: Polymorphism (alternative per tipo senza
if: Strategy, State), Pure Fabrication (classe non del dominio, per esempio un repository), Indirection (intermediario: Adapter, Facade, Proxy, Observer), Protected Variations (interfaccia stabile attorno al punto di variazione: Factory, Strategy). - Metodo: operazioni di sistema sequenza di messaggi per ogni passo: creazione = Creator, calcolo = Expert, coordinamento = Controller controllare accoppiamento e coesione giustificare ogni assegnazione e le alternative scartate.
- Errori tipici: classe "dio" che sa tutto; oggetti-contenitore senza comportamento; controller con la logica di business; mettere nel
Prestitoaccesso al database solo perché "ha i dati"; non giustificare le assegnazioni con il principio.