Architettura del software - decisioni, organizzazione e stili
In questa pagina 5
Nota sulle fonti. Il materiale del corso su questo argomento è ridotto (il programma lo elenca, le slide e la dispensa lo toccano solo di passaggio con MVC, publish-subscribe e client-server). La nota è quindi una sintesi concisa dei concetti standard di architettura del software, collegata agli esempi del corso e alle note sugli stessi stili in altre materie.
Definizione (architettura del software). L'organizzazione fondamentale di un sistema: i suoi componenti (sottosistemi, moduli), le relazioni tra loro e con l'ambiente, la loro distribuzione sulla rete e i principi che ne guidano il progetto e l'evoluzione.
Nel processo di sviluppo (Processi di sviluppo - modelli plan-based, metodi agili e ScrumUn processo software è un insieme strutturato di attività (specifica, progetto e realizzazione, validazione, evoluzione) e si descrive con attività, prodotti, ruoli e condizioni; i processi plan-driven pianificano tutto in anticipo e misurano il progresso sul piano (cascata, modello a V con i livelli di test corrispondenti, sviluppo incrementale pianificato, integrazione e configurazione di componenti, con prototipi e consegna incrementale per gestire il cambiamento, e miglioramento del processo con i livelli di maturità SEI), mentre i metodi agili pianificano in modo incrementale e accolgono il cambiamento (manifesto agile, XP con i suoi valori e le sue dodici pratiche, storie dell'utente e story point); Scrum organizza il lavoro in sprint con tre ruoli (Product Owner, Scrum Master, team), eventi (planning, daily scrum, review, retrospective) e artefatti (product backlog, sprint backlog, incremento), stimando con il planning poker; non c'è un processo giusto, ma priorità diverse (stabilità dei requisiti, criticità, dimensione e distribuzione del team).Processi di sviluppo - modelli plan-based, metodi agili e Scrum →) il progetto architetturale è la prima fase del progetto: collega 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 →) al progetto dettagliato dei componenti, e i design pattern (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 →) lavorano a un livello più basso, dentro i singoli componenti. Perché si fa un'architettura esplicita:
- comunicazione tra gli stakeholder (è il modello più astratto da discutere);
- analisi del sistema: le decisioni architetturali determinano prestazioni, affidabilità, manutenibilità;
- riuso su larga scala: si riusano interi stili e sistemi, non solo classi.
Decisioni di progettazione architetturale
Non esiste una ricetta: l'architetto risponde a domande, in parte in cooperazione con i requisiti.
- Esiste un'architettura di riferimento per questo tipo di applicazione? Come si distribuisce il sistema (un nodo, client e server, cloud)?
- Quali stili si usano (vedi sotto)? Come si decompone in componenti e come questi comunicano (chiamate, eventi, dati condivisi)?
- Quale strategia di controllo: centralizzato (un componente chiama gli altri) o basato su eventi?
- Come si valuta l'architettura rispetto ai requisiti non funzionali, e come si documenta?
Le decisioni principali vengono dai requisiti non funzionali:
| requisito | scelta architetturale tipica |
|---|---|
| prestazioni | componenti critici grandi e vicini (poche comunicazioni), dati locali, replica |
| sicurezza | struttura a livelli con i dati critici nei livelli più interni, controlli ad ogni confine |
| sicurezza di funzionamento (safety) | funzioni critiche in pochi componenti piccoli e verificabili |
| disponibilità | componenti ridondanti, sostituzione senza fermare il sistema |
| manutenibilità | componenti piccoli, autonomi, con interfacce stabili; niente dati condivisi in modo implicito |
Le richieste sono in conflitto: componenti grandi e vicini aiutano le prestazioni ma peggiorano la manutenibilità; più livelli aiutano la sicurezza ma rallentano. L'architetto sceglie un compromesso e lo documenta.
Viste
Un solo diagramma non basta. Il modello "4+1" propone:
Organizzazione del sistema: stili architetturali
Definizione (stile architetturale). Uno schema ricorrente di organizzazione di un sistema, con tipi di componenti, tipi di collegamenti tra componenti e vincoli sul loro uso. È ai componenti ciò che un design pattern è alle classi, ma a scala maggiore.
L'esempio a livelli è nel programma sotto (biblioteca): la presentazione conosce solo il servizio, il servizio conosce solo l'interfaccia CatalogoRepository, e l'implementazione dei dati (CatalogoInMemoria) si può sostituire senza toccare il resto. L'unico punto che conosce le classi concrete è main. Questa è l'inversione delle dipendenze (si dipende dalle interfacce, non dalle classi concrete, 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 →) applicata a livello di architettura.
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/* ===== Livello DOMINIO (dipende da nessuno) ===== */
class Documento {
final String titolo; int copie;
Documento(String titolo, int copie) { this.titolo = titolo; this.copie = copie; }
}
/* ===== Livello DATI: l'interfaccia sta SOPRA, l'implementazione sotto (Dependency Inversion) ===== */
interface CatalogoRepository {
Optional<Documento> trova(String titolo);
List<Documento> tutti();
}
class CatalogoInMemoria implements CatalogoRepository { // domani: CatalogoSql, senza toccare il resto
private final List<Documento> dati = new ArrayList<>(List.of(new Documento("rosa", 1), new Documento("pendolo", 0)));
public Optional<Documento> trova(String titolo) { return dati.stream().filter(d -> d.titolo.equals(titolo)).findFirst(); }
public List<Documento> tutti() { return dati; }
}
/* ===== Livello LOGICA DI BUSINESS: usa solo l'interfaccia del livello sotto ===== */
class PrestitoService {
private final CatalogoRepository repo;
PrestitoService(CatalogoRepository repo) { this.repo = repo; }
String presta(String titolo) {
Optional<Documento> d = repo.trova(titolo);
if (d.isEmpty()) return "sconosciuto";
if (d.get().copie == 0) return "non disponibile";
d.get().copie--;
return "ok";
}
}
/* ===== Livello PRESENTAZIONE: parla solo con il livello sotto ===== */
class Console {
private final PrestitoService servizio;
Console(PrestitoService servizio) { this.servizio = servizio; }
void comando(String riga) {
String[] p = riga.split(" ");
if (p[0].equals("presta")) System.out.println("> " + riga + " -> " + servizio.presta(p[1]));
else System.out.println("> " + riga + " -> comando non valido");
}
}
public class Livelli {
public static void main(String[] args) {
// l'unico punto che conosce le classi concrete di tutti i livelli (composizione dell'applicazione)
Console c = new Console(new PrestitoService(new CatalogoInMemoria()));
c.comando("presta rosa");
c.comando("presta rosa");
c.comando("presta pendolo");
c.comando("presta xyz");
c.comando("elimina rosa");
}
}> presta rosa -> ok
> presta rosa -> non disponibile
> presta pendolo -> non disponibile
> presta xyz -> sconosciuto
> elimina rosa -> comando non validoPer lo stile pipe and filter, ogni filtro del programma ha un solo compito (scartare le righe inutili, controllarne il formato, calcolare la multa) e conosce solo il formato dei propri ingressi e uscite; la catena di stream() è la pipe.
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
/** Pipe and filter: ogni filtro e' indipendente e conosce solo il formato dei dati in ingresso e uscita. */
public class PipeFiltri {
// formato: "utente;titolo;giorniRitardo"
static final List<String> ingresso = List.of(
"anna;rosa;0", "luca;pendolo;12", "anna;baudolino;3", "", "# commento", "piero;rosa;x", "luca;rosa;40");
record Riga(String utente, String titolo, int ritardo) { }
// filtri
static boolean utile(String s) { return !s.isBlank() && !s.startsWith("#"); } // scarta righe vuote e commenti
static Riga analizza(String s) { String[] p = s.split(";"); return new Riga(p[0], p[1], Integer.parseInt(p[2])); }
static boolean inRitardo(Riga r) { return r.ritardo() > 0; }
static String multa(Riga r) { return String.format(java.util.Locale.ROOT, "%s: %.2f euro (%s)", r.utente(), Math.min(0.10 * r.ritardo(), 3.0), r.titolo()); }
public static void main(String[] args) {
// la "pipe" e' la catena di stage dello stream
List<String> uscita = ingresso.stream()
.filter(PipeFiltri::utile)
.filter(s -> s.split(";")[2].matches("\\d+")) // scarta le righe malformate
.map(PipeFiltri::analizza)
.filter(PipeFiltri::inRitardo)
.map(PipeFiltri::multa)
.collect(Collectors.toList());
uscita.forEach(System.out::println);
// lo stesso concetto, composto esplicitamente: filtri come funzioni
Function<String, String> maiuscole = String::toUpperCase;
Function<String, String> senzaSpazi = s -> s.replace(" ", "");
System.out.println(maiuscole.andThen(senzaSpazi).apply("il nome della rosa"));
}
}luca: 1.20 euro (pendolo)
anna: 0.30 euro (baudolino)
luca: 3.00 euro (rosa)
ILNOMEDELLAROSAQuasi mai un sistema reale usa un solo stile: un'applicazione web è a livelli (browser, server, database), il livello server può usare MVC, le componenti di notifica publish-subscribe, e l'integrazione con altri sistemi client-server o REST.
Stili di modularizzazione
La modularizzazione è la scomposizione in parti con confini ben definiti. Principi:
- Nascondere l'informazione (information hiding): ogni modulo nasconde dietro un'interfaccia le decisioni di progetto che possono cambiare (struttura dati interna, algoritmo); chi lo usa dipende solo dall'interfaccia.
- Alta coesione, basso accoppiamento: le parti di un modulo servono a un unico scopo e i moduli dipendono poco l'uno dall'altro (sono i principi GRASP di Principi GRASP per l'assegnazione delle responsabilitàGRASP (General Responsibility Assignment Software Patterns) sono principi per assegnare le responsabilità alle classi, dove una responsabilità è un obbligo di conoscere (knowing) o di fare (doing): Creator (chi crea A? la classe che contiene o registra A, la usa da vicino o ha i dati per inizializzarla), Information Expert (la classe che ha le informazioni necessarie), Low Coupling (poche dipendenze tra classi per ridurre l'impatto dei cambiamenti), Controller (chi riceve gli eventi di sistema? un oggetto che rappresenta il sistema o il caso d'uso, che coordina e non calcola), High Cohesion (classi focalizzate su un unico scopo); si completano con Polymorphism, Pure Fabrication, Indirection e Protected Variations, che sono alla base di molti pattern GoF.Principi GRASP per l'assegnazione delle responsabilità →).
- Dipendenze senza cicli tra moduli e verso l'astrazione: nell'esempio a livelli la freccia di dipendenza va dall'alto verso il basso e sempre verso un'interfaccia.
- Interfacce piccole e stabili: l'interfaccia è il contratto; quello che sta dietro si può cambiare.
Modi di scomporre un sistema:
In Java i moduli si realizzano con i pacchetti (package, visibilità package-private), i moduli della piattaforma (module-info.java, che dichiara quali pacchetti si esportano) e i file jar; gli strumenti di build gestiscono le dipendenze tra moduli (Strumenti di sviluppo - git, Maven e DockerTre strumenti rendono un progetto web riproducibile e condivisibile. Git tiene la storia del codice: tre aree (cartella di lavoro, area di staging, repository), il commit è un'istantanea con un hash, il branch è un puntatore mobile a un commit, HEAD indica dove si è; il merge fonde due rami (fast-forward se non c'è divergenza, altrimenti un commit di merge) e può produrre conflitti, che si risolvono a mano nei marcatori di conflitto, poi add e commit; con un remoto si usano clone, fetch, pull (fetch + merge) e push, e un push rifiutato (fetch first) richiede prima un pull; in gruppo si lavora su rami di funzionalità con commit piccoli e main sempre funzionante. Maven descrive il progetto nel pom.xml (coordinate groupId:artifactId:version, packaging war, dipendenze con scope compile, provided, runtime, test), impone la struttura src/main/java, src/main/webapp, src/test, e il ciclo di vita validate, compile, test, package, verify, install, deploy: ogni fase esegue prima tutte le precedenti, mvn clean package produce target/aule.war. Docker confeziona l'applicazione: un'immagine (strati di sola lettura, costruita da un Dockerfile) è il modello, un container è un'istanza in esecuzione; per l'esempio un Dockerfile a due fasi compila il war con Maven e lo copia in Tomcat, e docker compose avvia insieme Tomcat e PostgreSQL con un volume per i dati.Strumenti di sviluppo - git, Maven e Docker →).
Strategie di controllo
Dall'architettura ai pattern
I pattern GoF realizzano nel piccolo le stesse idee: Facade è il confine di un sottosistema, Observer è il publish-subscribe a livello di oggetti, Proxy e Adapter nascondono componenti remoti o incompatibili, Abstract Factory sceglie una famiglia di implementazioni in un punto solo. Il Singleton serve per un componente unico (configurazione, pool di connessioni), da trattare con attenzione nelle applicazioni concorrenti.
Domande d'esame
1. Un sistema deve rispondere in meno di 100 ms con migliaia di richieste al secondo ed essere facile da modificare. Quali conflitti ci sono e come si risolvono? Traccia. Le prestazioni spingono verso pochi componenti grandi e vicini, dati locali e replicati; la manutenibilità verso componenti piccoli e autonomi con interfacce stabili. Compromesso: stile a livelli con pochi livelli, cache dietro l'interfaccia del repository, confini dei moduli sulle parti che cambiano meno; si misura l'effetto con test di prestazione e si documenta la scelta.
2. Confrontare lo stile a livelli e il publish-subscribe: componenti, accoppiamento, quando usarli. Traccia. Livelli: ogni livello usa solo quello sotto tramite interfaccia, accoppiamento verticale, adatto a separare presentazione, logica e dati. Publish-subscribe: i componenti comunicano tramite eventi senza conoscersi (accoppiamento minimo), adatto a notifiche e sistemi distribuiti, ma con flusso di controllo meno prevedibile e consegna da garantire.
3. Che cosa significa "dipendere dalle astrazioni" in un'architettura a livelli? Fare un esempio in Java.
Traccia. Il livello superiore usa un'interfaccia (CatalogoRepository) e non la classe concreta (CatalogoInMemoria); l'implementazione si fornisce dall'esterno (in main, o con un contenitore di dependency injection). Cambiare la sorgente dati non richiede di modificare né ricompilare i livelli superiori.
Versione ripasso
- Architettura: componenti (sottosistemi, moduli), relazioni, distribuzione, principi guida. È il passaggio tra requisiti e progetto dettagliato; i pattern GoF lavorano dentro i componenti. Serve per comunicare, analizzare (prestazioni, affidabilità, manutenibilità) e riusare su larga scala. Fonti del corso scarse: sintesi di concetti standard.
- Decisioni: architettura di riferimento? distribuzione? stili? decomposizione e comunicazione? controllo centralizzato o a eventi? valutazione e documentazione?
| requisito | scelta |
|---|---|
| prestazioni | componenti critici grandi e vicini, dati locali |
| sicurezza | livelli, dati critici all'interno, controlli ai confini |
| safety | funzioni critiche in pochi componenti piccoli |
| disponibilità | ridondanza |
| manutenibilità | componenti piccoli e autonomi, interfacce stabili |
I requisiti entrano in conflitto (prestazioni contro manutenibilità): si sceglie un compromesso e lo si documenta.
Viste (4+1): logica, processi, sviluppo, fisica (deployment), più gli scenari (casi d'uso).
Stili: a livelli (ognuno usa solo il sotto), MVC (model, view, controller: 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 →), repository (archivio condiviso; collo di bottiglia), client-server, pipe and filter (filtri indipendenti), publish-subscribe (eventi, componenti che non si conoscono), microservizi (servizi piccoli e autonomi). Un sistema reale li combina (web: livelli + MVC + notifiche a eventi).
stile vantaggio svantaggio livelli sviluppo per strati, livelli sostituibili ogni richiesta attraversa tutti i livelli repository un solo archivio per strumenti diversi collo di bottiglia, punto unico di guasto client-server servizi condivisi e dati centralizzati server come punto di guasto, dipendenza dalla rete pipe and filter filtri riusabili e componibili formato comune, poco adatto all'interattivo publish-subscribe componenti che non si conoscono consegna e ordine degli eventi da garantire microservizi scalabilità e rilascio per componente complessità di rete e di dati distribuiti Esempio a livelli (
Livelli):ConsolePrestitoServiceCatalogoRepository(interfaccia)CatalogoInMemoria; solomainconosce le classi concrete; uscite:presta rosa -> ok,presta rosa -> non disponibile,presta pendolo -> non disponibile,presta xyz -> sconosciuto,elimina rosa -> comando non valido.
interface CatalogoRepository { Optional<Documento> trova(String titolo); List<Documento> tutti(); }
class PrestitoService {
private final CatalogoRepository repo;
PrestitoService(CatalogoRepository repo) { this.repo = repo; } // dipende dall'interfaccia
}
Console c = new Console(new PrestitoService(new CatalogoInMemoria())); // l'unico punto che sceglie- Pipe and filter (
PipeFiltri):filter(utile),filter(formato),map(analizza),filter(inRitardo),map(multa); ogni filtro conosce solo il formato di ingresso e uscita. - Modularizzazione: information hiding (decisioni che cambiano nascoste dietro interfacce), alta coesione e basso accoppiamento (Principi GRASP per l'assegnazione delle responsabilitàGRASP (General Responsibility Assignment Software Patterns) sono principi per assegnare le responsabilità alle classi, dove una responsabilità è un obbligo di conoscere (knowing) o di fare (doing): Creator (chi crea A? la classe che contiene o registra A, la usa da vicino o ha i dati per inizializzarla), Information Expert (la classe che ha le informazioni necessarie), Low Coupling (poche dipendenze tra classi per ridurre l'impatto dei cambiamenti), Controller (chi riceve gli eventi di sistema? un oggetto che rappresenta il sistema o il caso d'uso, che coordina e non calcola), High Cohesion (classi focalizzate su un unico scopo); si completano con Polymorphism, Pure Fabrication, Indirection e Protected Variations, che sono alla base di molti pattern GoF.Principi GRASP per l'assegnazione delle responsabilità →), dipendenze senza cicli e verso astrazioni, interfacce piccole e stabili. Scomposizione per funzione, per oggetti, per livelli, per componenti. Java:
package, moduli (module-info.java), jar, build con Strumenti di sviluppo - git, Maven e DockerTre strumenti rendono un progetto web riproducibile e condivisibile. Git tiene la storia del codice: tre aree (cartella di lavoro, area di staging, repository), il commit è un'istantanea con un hash, il branch è un puntatore mobile a un commit, HEAD indica dove si è; il merge fonde due rami (fast-forward se non c'è divergenza, altrimenti un commit di merge) e può produrre conflitti, che si risolvono a mano nei marcatori di conflitto, poi add e commit; con un remoto si usano clone, fetch, pull (fetch + merge) e push, e un push rifiutato (fetch first) richiede prima un pull; in gruppo si lavora su rami di funzionalità con commit piccoli e main sempre funzionante. Maven descrive il progetto nel pom.xml (coordinate groupId:artifactId:version, packaging war, dipendenze con scope compile, provided, runtime, test), impone la struttura src/main/java, src/main/webapp, src/test, e il ciclo di vita validate, compile, test, package, verify, install, deploy: ogni fase esegue prima tutte le precedenti, mvn clean package produce target/aule.war. Docker confeziona l'applicazione: un'immagine (strati di sola lettura, costruita da un Dockerfile) è il modello, un container è un'istanza in esecuzione; per l'esempio un Dockerfile a due fasi compila il war con Maven e lo copia in Tomcat, e docker compose avvia insieme Tomcat e PostgreSQL con un volume per i dati.Strumenti di sviluppo - git, Maven e Docker →. - Controllo: chiamata e ritorno, gestore centralizzato, a eventi (Pattern comportamentali - Observer, Strategy, Command, Iterator e Template MethodI pattern comportamentali descrivono come gli oggetti collaborano e si dividono le responsabilità: Observer (publish-subscribe) avvisa automaticamente i dipendenti quando il soggetto cambia stato, mantenendo basso l'accoppiamento (si registrano e cancellano a runtime, modello push o pull, attenzione alle modifiche durante la notifica); Strategy rende intercambiabili gli algoritmi dietro un'interfaccia (in Java anche con una lambda); Command trasforma la richiesta in un oggetto, quindi memorizzabile, accodabile e annullabile (undo); Iterator scorre un aggregato senza esporne la struttura (for-each, fail-fast); Template Method fissa lo scheletro di un algoritmo e lascia alle sottoclassi passi astratti o hook (principio di Hollywood); State, Visitor, Chain of Responsibility, Mediator, Memento e Interpreter completano le undici forme del catalogo.Pattern comportamentali - Observer, Strategy, Command, Iterator e Template Method →).
- Dai pattern all'architettura: Facade (confine di un sottosistema), Observer (publish-subscribe fra oggetti), Proxy e Adapter (componenti remoti o incompatibili), Abstract Factory (una famiglia di implementazioni scelta in un punto), Singleton (componente unico).
- Errori tipici: scegliere uno stile prima di guardare i requisiti non funzionali; livelli che saltano il livello sotto; dipendenze da classi concrete e cicli tra moduli; repository condiviso come unico canale; pensare che i pattern GoF sostituiscano l'architettura.
Teoria collegata
- Design pattern - introduzione e catalogo GoF
- Ingegneria dei requisiti - requisiti funzionali e non funzionali
- Ingegneria del software - prodotto software e processo di sviluppo
- Pattern comportamentali - Observer, Strategy, Command, Iterator e Template Method
- Pattern strutturali - Adapter, Decorator, Composite, Facade e Proxy
- Principi GRASP per l'assegnazione delle responsabilità
- Processi di sviluppo - modelli plan-based, metodi agili e Scrum