Patterns
Un pattern identifica e assegna un nome a strutture e comportamenti ricorrenti
Incanala la conoscenza di un'architettura ottenuta da esperti attraverso le loro esperienze di design
Patterns
Col tempo pattern utili si evolvono in features dei linguaggi
- Assembly language patterns: closed subroutines, control structures, portano alla nascita di C e FORTRAN
- Information hiding patterns: incapsulamento, linguaggi orientati agli oggetti (Ada, C++, Java)
- Meta-programming patterns -> C++ templates
- Iterator patterns -> C++ range based for loops
- Monitor Object -> Java synchronized block
Patterns
Lo scopo dei pattern
- Riutilizzo di design e architetture ricorrenti
- Vocabolario comune che aiuta i team a comunicare in maniera efficiente riguardo il design, evitando considerazioni su componenti di basso livello nelle fasi di strutturazione dell'architettura
Patterns
... sviluppiamo una classe che implementa l'interfaccia X, e che ottiene come parametro un'implementazione di Y, chiamando i suoi metodi in maniera da creare un ponte tra le due interfacce.
Patter unaware team
... sviluppiamo un adapter da Y a X.
Pattern aware team
Patterns
Nell'ambito dello sviluppo software si distinguono due tipologie di pattern:
- Design patterns: forniscono una soluzione a un problema con contesto ridotto, in maniera molto specifica
- Architectural patterns: consistono in una visione d'insieme e in un processo per la risoluzione di problemi di software design in un particolare dominio
Patterns ARE SOCIAL
Pattern descritti senza riferimenti ad alcun altro pattern
sono molto rari
La maggior parte dei pattern è un componente compatibile, o sostitutivo, di un contesto in cui cooperano numerosi pattern
Patterns ARE SOCIAL
PATTERN COMPLEMENTS
Sono pattern che si presentano in coppie, sia in termini di cooperazione che di contrapposizione
- Cooperazione: Factory Method - Disposal Method
- Contrapposizione: Iterator - Batch Method
Patterns ARE SOCIAL
PATTERN COMPOUNDS
Alcuni pattern possono essere utilizzati insieme, presi come singola decisione in risposta a un design ricorrente
Batch Iterator
Patterns are social
PATTERN SEQUENCES
Catena di pattern in cui ogni predecessore fornisce il contesto in cui viene applicato il pattern successivo.
Un pattern viene aggiunto alla sequenza per aumentare i vantaggi o ammortizzare gli effetti collaterali del precedente.
Patterns are social
PATTERN SEQUENCES
Ad esempio
-
Strategy: fornisce gli agganci a dei metodi contenenti parti configurabili di un algoritmo
-
Abstract Factory: permette il raggruppamento di strategie semanticamente compatibili
-
Component Configurator: configurazione dinamica dei componenti e delle loro connessioni
Patterns are social
PATTERN LANGUAGES
Una network in cui i pattern sono costruiti uno sull'altro, per aiutare lo sviluppatore a generare un sistema documentando successive scelte di design, trasformazioni e alternative
Un pattern language può essere visto come l'intersezione di più pattern sequences, fornendo diramazioni per applicare diverse scelte di design in base alle necessità
Patterns are social
PATTERN LANGUAGES
- Individuazione dei problemi chiave e del loro ordine di risoluzione
- Come gestire le dipendenze tra i vari problemi che vanno affrontati
- Come risolvere ciascun singolo problema efficaciemente nel suo contesto
- Quali alternative esistono per la risoluzione di ciascun problema
Patterns are social
PATTERN LANGUAGES
Patterns
I pattern non sono fatti di codice o individuati da un design concreto: devono essere reificati e applicati in particolari linguaggi
Patterns
Un pattern è individuato attraverso:
- Nome e definizione del suo scopo
- Problema affrontato dal pattern
- Soluzione, ovvero la descrizione della struttura e delle dinamiche del pattern
Over-Engineering
Non tutto ciò che riguarda lo sviluppo di un'architettura è da considerarsi un pattern:
alcune parti sono solo algoritmi o semplice design.
Inoltre prima di fare uso di un pattern è necessario valutare gli specifici benefici contro gli effetti collaterali negativi
e dimostrare la sua applicabilità.
Scegliere la soluzione più semplice e applicare i pattern dove emergono le condizioni per il loro utilizzo.
Design Patterns
Lista di alcuni design pattern comuni
e di applicabilità generale
Strategy pattern
Definisce una famiglia di algoritmi,
incapsulati in modo da essere intercambiabili indipendentemente dal client* che ne fa uso.
*client: parte di un'applicazione che dipende dal componente
Strategy pattern
-
Permette di incapsulare le parti variabili di un'applicazione, in modo che i cambiamenti non coinvolgano il resto del codice
- Favorisce la composizione rispetto all'ereditarietà
-
Dependency Inversion: programmare verso un'astrazione piuttosto che verso un'implementazione concreta
Strategy pattern
public interface WelcomeMessageStrategy {
String welcome(String userName);
}
private WelcomeMessageStrategy welcomeStrategy;
public void welcomeWhenUserLogsIn(String userName) {
System.out.println(welcomeStrategy.welcome(userName));
}
Strategy pattern
public class HelloMessageStrategy implements WelcomeMessageStrategy {
@Override
public String welcome(String userName) {
return "Hello " + userName + "!";
}
}
public class GTFOMessageStrategy implements WelcomeMessageStrategy {
@Override
public String welcome(String userName) {
return "Burn in a f**king fire " + userName + "!!!";
}
}
Observer Pattern
Definisce una dipendenza uno a molti tra oggetti in modo che, quando il subject cambia stato, gli observer vengano automaticamente aggiornati.
Esistono due modalità di informare gli oggetti iscritti:
- push: invio del contesto (nuovo valore dello stato, a volte anche del vecchio stato)
- pull: semplice notifica di cambiamento, è compito degli observer recuperare le informazioni sullo stato del subject
Observer pattern
L'Observer Pattern permette un design dove subject e observer sono loosely coupled:
possono interagire avendo la minima conoscenza necessaria l'uno dell'altro.
Ridurre l'interdipendenza tra oggetti aumenta la flessibilità di un'architettura
Decorator Pattern
Aggiunge dinamicamente ulteriori funzionalità ad un oggetto.
Alternativa più flessibile dell'ereditarietà
per l'estensione di funzionalità.
Per essere usato, il client non deve affidarsi al tipo concreto di un componente.
Decorator Pattern
#RealWorldDecorators
InputStream
Astrazione di un flusso di byte
Decorator pattern
#RealWorldDecorators
Implementazioni di InputStream
- FileInputStream: legge i byte da un file
- StringBufferInputStream: legge i byte da una stringa
- ByteArrayInputStream: legge i byte da un array
- FilterInputStream: decoratore che aggiunge funzionalità alla semplice lettura di byte uno per volta
Decorator pattern
#RealWorldDecorators
Implementazioni di FilterInputStream
- PushbackInputStream: consente di annullare la lettura di un byte
- BufferedInputStream: legge chunk di più byte per volta
- DataInputStream: converte i byte letti in oggetti
- LineNumberInputStream: tiene conto del numero di linee
Factory pattern
Permette di disaccoppiare la creazione di un oggetto
dalla sua specifica implementazione.
Promuove la dependency inversion:
evita la dipendenza da tipi concreti
Factory pattern
Esistono due tipologie di Factory Pattern:
- Factory Method: definisce un metodo astratto per la creazione di un oggetto, lasciando alle sottoclassi la decisione di che implementazione instanziare.
- Abstract Factory: fornisce un'interfaccia per creare una famiglia di oggetti semanticamente compatibili, senza dover specificare le loro classi concrete.
Factory Pattern
L'Abstract Factory Pattern può essere visto come specializzazione dello Strategy Pattern
nella creazione di oggetti
Il Factory Method Pattern è un modo naturale spesso utilizzato di implementare un'Abstract Factory:
ciascun metodo dell'Abstract Factory è un Factory Method
Command Pattern
Incapsula una richiesta in un oggetto, in modo da poter essere aggiunta dinamicamente in una parte di un algoritmo o in una coda di esecuzione.
Ad esempio:
- callback: Listener registrati per essere eseguiti all'occorrenza di un dato evento
- computation: Runnable eseguiti da una thread pool
Adapter Pattern
Converte l'interfaccia di una classe in un'altra interfaccia richiesta dal client.
Elimina l'incompatibilità tra interfacce diverse.
Usato dove esiste un layer di astrazione, in modo da adattare le classi della libreria utilizzata a tale astrazione.
Questo serve a minimizzare la dipendenza da una libreria esterna, impiegando gli adapter come connettori
Adapter pattern
#RealWorldAdapters
Nelle vecchie versioni di Java, l'Iterator Pattern era implementato attraverso l'interfaccia Enumeration
public interface Enumeration<E> {
boolean hasMoreElements();
E nextElement();
}
Adapter pattern
#RealWorldAdapters
Attualmente l'implementazione del pattern è data dall'interfaccia Iterator
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
Adapter pattern
#RealWorldAdapters
/* Costruttore e annotazioni @Override omesse */
public class EnumerationIterator<E> implements Iterator<E> {
private final Enumeration<? extends E> enumeration;
public boolean hasNext() {
return enumeration.hasMoreElements();
}
E next() {
return enumeration.nextElement();
}
void remove() {
throw new UnsupportedOperationException("read only iterator");
}
}
Facade Pattern
Fornisce un'interfaccia che raggruppa quelle di un sottosistema, in modo da semplificarne l'accesso.
Utile per disaccoppiare il client
da un sottosistema complesso.
Null Object Pattern
Si tratta di fornire un'implementazione che funga da elemento neutro.
Risparmia dal dover fare controlli sui riferimenti a null
e evita il rischio di errori a runtime a causa di null references.
Null Object Pattern
Alcuni esempi
- Empty Iterator: nel caso una collection sia vuota, invece di restituire null si restituisce un iteratore senza elementi su cui iterare
- NOP Command: nel caso non deve essere eseguita alcuna callback, invece di avere un null command si utilizza uno con un metodo vuoto
The dark side
Darth Sidious, Imperatore Galattico
implementazione di Singleton Pattern
Antipatterns
Quasi tutti i pattern hanno degli effetti collaterali o carenze, ma per alcuni di questi si ha un degrado dell'architettura non giustificabile dai vantaggi che il pattern porta
In questo caso vengono definiti antipattern, ma è ugualmente importante conoscerli per poterli evitare
Dalla storia si devono imparare sia successi che errori
Antipatterns
Un antipattern descrive come giungere da un problema
a una cattiva soluzione
Inoltre specifica:
- perché una cattiva soluzione è attrattiva
- perché la soluzione a lungo termine risulta essere cattiva
- quali pattern forniscono un'alternativa buona soluzione
Service Locator
Permette di ottenere le implementazioni di classi astratte, dette servizi, da un unico punto centrale.
È possibile fare dependency injection richiedendo le dipendenze direttamente al service locator.
Non è necessario che il client sia a conoscenza della libreria utilizzata, in quanto il service locator fa da collegamento.
I servizi possono essere sostituiti a runtime.
Service Locator
Viene completamente nascosto il grafo delle dipendenze, rendendo i componenti che fanno riferimento
al service locator difficilmente manutenibili.
Nel caso non vengano forniti dei servizi, si hanno errori a run time invece che a compile time o startup time.
Siccome il registro dei servizi deve essere unico, si ha un bottleneck in caso di applicazioni multithreaded.
Riportare l'equilibrio nella forza
Gof (Gang of four)
Design Patterns:
Elements of Reusable
Object-Oriented Software
1994
POSA 1 2 3 4 5
Pattern-Oriented Software Architecture
1996 - 2007
POSA 2 Course
Douglas Schmidt
Professor of Computer Science, Vanderbilt University
POSA 2, GOF and Android lessons on coursera.org
Grazie!
@0x1abe5
@unitsdev