Title: Introduzione ai design pattern
1Introduzione ai design pattern
2Cosa sono i design pattern
- I problemi incontrati nello sviluppare grossi
progetti software sono spesso ricorrenti e
prevedibili. - I design pattern sono schemi utilizzabili nel
progetto di un sistema - Permettono quindi di non inventare da capo
soluzioni ai problemi gia risolti, ma di
utilizzare dei mattoni di provata efficacia - Inoltre, un bravo progettista sa riconoscerli
nella documentazione o direttamente nel codice, e
utilizzarli per comprendere in fretta i programmi
scritti da altri - forniscono quindi un vocabolario comune che
facilita la comunicazione tra progettisti
3Design pattern nella libreria Java
- I pattern sono utilizzati pervasivamente dalle
classi standard di Java, e sono alla base della
progettazione orientata agli oggetti - Es. Iterator fornisce un modo efficiente e
uniforme per accedere a elementi di collezioni - Altri esempi presentati in queste slide
- Abstract Factory, Singleton, Flyweight, State,
Strategy, Proxy, Adaptor e Decorator
4Abstract Pattern
- Costruire implementazioni multiple di una stessa
classe - Es. Poly densi e sparsi
- DensePoly una implementazione di Poly adatta al
caso in cui ci sono pochi coefficienti nulli (ad
es. quella vista per i Poly con un array per
tutti i coefficienti) - SparsePoly una diversa implementazione,
efficiente quando molti coefficienti sono nulli
(es. lista a puntatori, in cui ogni nodo
memorizza il coeff. e il grado di ogni termine
!0). - Poi però se Dense e Sparse sono tipi distinti
dovremmo definire codice diverso per ogni
polinomio - public static void DensePoly derivata (DensePoly
p) - public static void SparsePoly derivata
(SparsePoly p) - ma differenza fra Dense e Sparse è solo
implementativa
5Poly Astratta e impl. multiple
- Soluzione definire una classe Poly e definire
DensePoly e SparsePoly come sue estensioni (pure) - Utilizzatore di Poly vede solo i metodi
definiti in Poly. - //_at_ ensures ( \result derivata di p )
- public static Poly derivata(Poly p)
- Non importa se a runtime p sarà un DensePoly o
uno SparsePoly. - Poly non contiene un rep (perche non vi molto in
comune fra le due implementazioni) saranno
sottoclassi a stabilire modalità di
memorizzazione - Quindi Poly deve diventare astratta non è
possibile fare add, ecc. senza il rep. Gerarchia
di tipi può essere utilizzata per fornire più
implementazioni dello stesso tipo - Il tipo da implementare è di solito descritto con
interfaccia (se nessuna operazione è
implementabile) o classe astratta (se alcune
operazioni sono implementabili)
6Creazione di oggetti?
- Il codice di un programma orientato agli oggetti
non dipende dalla precisa classe cui appartiene
un certo oggetto. I programmi richiedono a un
oggetto solo il rispetto del contratto
corrispondente alla sua specifica (il suo tipo) - Limitare le dipendenze dalle classi è
desiderabile perché permette di sostituire
unimplementazione con unaltra. es si può usare
Poly e poi se si passa una DensePoly o una
SparsePoly tutto funziona lo stesso - Eccezione le chiamate ai costruttori il codice
utente che chiama il costruttore di una
determinata classe rimane vincolato a quella
classe - Ci piacerebbe potere lasciare alla classe Poly
stessa la scelta se il tipo da costruire e' uno
SparsePoly o un DensePoly!
7Factory Method
- La soluzione è nascondere la creazione in un
metodo detto factory restituisce un oggetto di
una classe senza essere costruttore di quella
classe - Esempio il metodo che restituisce loggetto
iteratore associato a un contenitore (nella
nomenclatura Liskov, oggetti generatori) e un
esemplare di una classe che implementa
linterfaccia Iterator, ma il metodo non e un
costruttore - In Java le chiamate ai costruttori non sono
personalizzabili. Una factory può invece
scegliere la strategia di allocazione.
8Factory (2)
- Il metodo può creare oggetti di classi diverse a
seconda dei parametri, ma tutti questi oggetti
avranno lo stesso tipo. - Esempio un polinomio del tipo axnb viene
implementato da una classe SparsePoly, mentre il
polinomio generico è un esemplare di
DensePoly.public static Poly createPoly (int
a) int degree -1, numCoeffs 0 for (int
n 0 n lt a.length n) if (an ! 0) - numCoeffs degree n if
((numCoeffs 2 a0 ! 0) numCoeffs
1) return new SparsePoly (degree, adegree,
a0) return new DensePoly (degree, a)
9Alternativa Factory Class
- A volte e' preferibile che il metodo statico sia
in una classe a parte - Es. public class FabbricaDiPoly public static
Poly createPoly (int a) ... - Ad es. puo' essere comodo per aggiungere
operazioni che influenzano che cosa si vuole
fabbricare o per non consentire la costruzione di
oggetti di tipo Poly a chi vede solo la classe
Poly
10Abstract Factory
- La soluzione non è ottimale dal punto di vista
dell'estendibilita' cosa succede se aggiungiamo
una classe PolyMezzoDenso che implementa un Poly
per i casi intermedi ne' densi ne' sparsi? - Dobbiamo modificare il metodo factory, violando
principio Open/Closed. - Allora si può usare Abstract Factory
- La Factory Class è astratta il metodo factory e'
astratto - C'e' un'erede concreta della Factory per ogni
classe concreta dell'implementazione, che
implementa il metodo giusto (FactoryDensePoly,
FactorySparsePoly) - Estendendo la classe Poly con PolyMezzoDenso ci
basta aggiungere una FactoryPolyMezzoDenso
11Abstract Factory descritto in UML
12Pattern per Ottimizzazioni comuni
- Alcuni pattern forniscono trucchi semplici e
funzionali per velocizzare un programma o ridurne
i requisiti di memoria. - A volte lutilizzo di questi pattern non fa parte
del progetto vero e proprio del sistema, ma un
programmatore competente sa riconoscere le
occasioni in cui usarli efficacemente
13Singleton
- A volte una classe contiene per definizione un
solo oggetto - e.g., una tabella, un archivio in cui si assume
che ogni elemento sia individuato univocamente
dal suo identificatore (quindi se ci fossero piu
tabelle non si avrebbe questa garanzia di
unicità) - Usare una normale classe con soli metodi statici
non assicura che esista un solo esemplare della
classe, se viene reso visibile il costruttore - In una classe Singleton il costruttore e
protetto o privato - Un metodo statico, o una factory, forniscono
laccesso alla sola copia delloggetto
14Singleton pattern il tipico codice
- public class SingletonClass
- private static SingletonClass s //the single
instance - public static SingletonClass getObject()
- //build the unique object only if it does not
exist already - if (s null) s new SingletonClass()
- return s
-
- private SingletonClass() // the constructor
- // other methods
15Flyweight
- Quando molti oggetti identici (e immutabili)
vengono utilizzati contemporaneamente, e utile
costruire solo un oggetto per ogni classe di
equivalenza di oggetti identici - gli oggetti condivisi vengono chiamati flyweight
(pesi mosca) perche spesso sono molto piccoli - Questo pattern va ovviamente usato solo se il
numero di oggetti condivisi e molto elevato - Gli oggetti flyweight devono essere immutabili
per evitare problemi di aliasing
16Flyweight implementazione del pattern
- Occorre una tabella per memorizzare gli oggetti
flyweight quando vengono creati - Non si possono usare i costruttori
- un costruttore costruisce sempre una nuova
istanza! - naturale usare una factory class per creare gli
oggetti - la factory deve controllare se loggetto
richiesto esiste già nella tabella prima di
crearlo se non esiste, chiama un costruttore
(privato!), altrimenti restituisce un reference
alloggetto esistente. - Se necessario, occorre rimuovere gli oggetti
dalla tabella quando non sono più utilizzati - Efficiente usare questo pattern se cè un alto
grado di condivisione degli oggetti - si risparmia memoria
- non si perde tempo a inizializzare oggetti
duplicati - si può usare per il confronto al posto di
equals.
17UML per Flyweight
18Esempio di pattern flyweight
- classe Word per rappresentare parole immutabili
in applicazioni di elaborazione testi - Public class Word
- //OVERVIEW Words are strings that provide
- //methods to produce them in various forms words
are immutable for - // each unique string there is at most one word
- private static Hashtable t //maps strings to
words - public static makeWord(String s) //factory
returns the word for string s - private Word(String s) //constructor of the
unique word for string s - public String mapWord(Context c)
- //returns the string corresponding to this in the
form - // suitable for context c
- // other word methods
19State
- A volte si vuole usare un'implementazione diversa
dello stesso oggetto durante la sua vita - per esempio, una classe vettore può usare una
rappresentazione diversa a seconda del numero
degli elementi. Se si usa una sola classe il
codice degli oggetti mutabili può diventare assai
complicato e pieno di condizionali - Razionalizzazione della struttura del codice gli
oggetti cambiano configurazione a seconda dello
stato in cui si trovano. Il pattern State
introduce un ulteriore strato tra il tipo
implementato e limplementazione - a un unico tipo si fanno corrispondere piu
classi che lo implementano, e che corrispondono a
diversi stati in cui possono trovarsi gli
esemplari del tipo - nel corso della vita delloggetto, possono essere
utilizzate diverse implementazioni senza che
lutente se ne accorga
20State (2)
- Implementazione del pattern
- Si crea uninterfaccia o una classe astratta che
rappresenta le parti delloggetto che possono
essere sostituite nel corso della vita
delloggetto - Ciascuna delle possibili rappresentazioni (stati)
diventa unimplementazione dellinterfaccia o un
erede della classe astratta - La classe principale conterrà il codice per
scegliere la rappresentazione più adatta e per
delegare limplementazione alla sottoclasse
piuappropriata per lo stato delloggetto
21Esempio di State
- Classe BoolSet, analogo dellIntset un insieme
di boolean che cambia implementazione a seconda
del numero di elementi si usano due classi
SmallBoolSet e BigBoolSet a seconda della
cardinalità dellinsieme - interface BoolSetState
- public boolean get (int n)
- throws IndexOutOfBoundsException
- public BoolSetState set (int n, boolean val)
- throws IndexOutOfBoundsException
-
- public class BoolSet
- BoolSetState s
- public BoolSet () BoolSetState new
SmallBoolSet () - public final boolean get (int n)
- throws IndexOutOfBoundsException return
s.get (n) - public final void set (int n, boolean val)
- throws IndexOutOfBoundsException s s.set
(n, val) -
22Esempio di State (2)
- SmallBoolSet usa un singolo long per implementare
set i cui elementi sono tutti minori di 64. - class SmallBoolSet implements BoolSetState
- public static final long MAX_SIZE 64
- long bitset
- public boolean get (int n)
- throws IndexOutOfBoundsException
- if (n lt 0)
- throw new ArrayIndexOutOfBoundsException(n)
- return n lt MAX_SIZE (bitset (1 ltlt n)) !
0 -
23Esempio di State (3)
- Se si imposta a 1 un elemento oltre il 64-esimo,
viene creato un BigBoolSet. - public BoolSetState set (int n, boolean val)
- throws IndexOutOfBoundsException
- if (n lt 0)
- throw new ArrayIndexOutOfBoundsException(n)
- if (val)
- if (n gt MAX_SIZE)
- return new BigBoolSet (this).set (n,
val) - bitset (1 ltlt n)
-
- else if (n lt MAX_SIZE)
- bitset (1 ltlt n)
- return this
-
-
24Esempio di State (4)
- Per la classe BigBoolSet vediamo solo il metodo
che - costruisce un BigBoolSet a partire da uno
SmallBoolSet - class BigBoolSet implements BoolSetState
- ...
- public BigBoolSet (SmallBoolSet s)
- for (i 0 i lt s.MAX_SIZE i)
- if (s.get (i))
- set (i, true)
-
- ...
-
25Procedure come oggetti
- Java non permette di utilizzare come oggetti le
chiamate a un metodo - Questo, tuttavia, può essere utile per definire
astrazioni altamente generiche ed estendibili
(pluggable) - Lunico modo di ottenere questo risultato è
definire classi o interfacce molto piccole.Ci
sono esempi nella libreria di classi di Java - Comparable
- Runnable
- ActionListener
26Strategy
- Il pattern Strategy fornisce un oggetto che
compie unoperazione precisa, richiesta
dallesterno - Per esempio, stabilire un ordinamento tra oggetti
- Loperazione è esprimibile con clausole Requires
e Ensures - Un esempio di questo pattern nellinterfaccia
Comparator di JDK 1.4
27UML
28Esempio di Strategy ordinamento di oggetti
qualunque
- Vogliamo ordinare un contenitore di oggetti
(p.es. un array) - La procedura di ordinamento è sempre la stessa
per tutti i tipi di oggetti possibili - vorremmo quindi fare un unico metodo per tutti i
tipi. Qualcosa come - public static void sort(Object s
- //_at_ensures ( s è ordinato )
- ma serve un modo per confrontare gli elementi
in s! Object non ha un metodo per il confronto e
quindi occorre definirlo da qualche altra parte - Idea aggiungo come argomento al metodo un
oggettino incaricato del confronto. - Per potere rendere il metodo sort applicabile a
ogni tipo, loggetto sarà di tipo interfaccia.
Quindi - definisco l'interfaccia Comparator (esiste
peraltro in java.util), che definisce
sintatticamente il confronto di due oggetti - fornisco una implementazione di Comparator per il
tipo che voglio ordinare (es. IntegerComparator) - Passo anche un Comparator quando chiamo la
procedura per confrontare gli elementi
29Interface Comparator
- interface Comparator //OVERVIEW immutabile
public int compare (Object o1, Object o2) - throws ClassCastException, NullPointerException
- /_at_ensures ( se o1 e o2 non sono di tipi
confrontabili - _at_ lancia ClassCastException
- _at_ altrimenti o1lto2 ? ret 1
- _at_ o1o2 ? ret 0
- _at_ o1gto2 ? ret 1
-
- NB
- interfaccia non è supertipo dei tipi i cui
elementi vanno comparati!
30metodo sort
- Argomento aggiuntivo un oggetto di tipo
Comparator (uno solo per tutti gli elementi!). - Esempio da java.util.Arrays
- public static void sort (Object a, Comparator
c) -
- if (c.compare(a.i, a.j)
-
-
- Es. di uso
- public class AlphabeticComparator implements
Comparator public int compare(Object o1, Object
o2) String s1 (String)o1 String s2
(String)o2 return s1.toLowerCase().compareTo(
s2.toLowerCase()) - ...String s new String30 ...
- Java.util.Arrays.sort(s, new AlphabeticComparator(
)) ...
31adattare interfacce diverse Proxy, Adaptor e
Decorator
- Molto spesso librerie diverse espongono
interfacce diverse per fare la stessa cosa - Windows e MacOS sono ambienti grafici
incompatibili tra loro - Una stessa soluzione si adatta a svariati
problemi - si scrivono nuove classi che impongano una stessa
interfaccia e uno stesso insieme di precondizioni
e postcondizioni - Gli esemplari delle nuove classi usano un oggetto
interno che contiene la vera implementazione - esempio del motto Every problem in computer
science can be solved by adding another level of
indirection - loggetto visibile all esterno si chiama oggetto
esterno
32Adaptor
- La strategia delineata nella slide precedente
prende il nome di Adaptor quando linterfaccia
delloggetto interno è diversa da quella
delloggetto esterno - Loggetto esterno e lAdapter, quello interno
lAdaptee. - le librerie di classi per linterfaccia grafica,
come AWT o Swing, non sono altro che enormi
raccolte di oggetti Adapter - in Java, java.io.OutputStreamWriter permette di
scrivere caratteri a 16-bit (Unicode) su di un
OutputStream che lavora per byte - gli skeleton di RMI mappano su di un protocollo
binario i metodi di uninterfaccia Java
33UML
34Proxy
- Quando loggetto interposto espone esattamente la
stessa interfaccia delloggetto separato, di cui
fa le veci, esso prende il nome di Proxy - java.util.zip.DeflaterOutputStream comprime
automaticamente i dati scritti - Scopo del Proxyposporre o addirittura evitare
listanziazione di oggetti pesanti, se non
necessaria - es. gli stub di RMI sembrano oggetti locali, ma
si occupano di serializzare i parametri, inviarli
in rete, attendere il risultato, ecc., senza però
essere i veri oggetti
35UML
36Documentazione UML del pattern Proxy
Client
Proxy
Server
1 request( )
2 preProcess( )
3
4 request( )
5
6 postProcess( )
Some private processing
operations
7
37Decorator
- Altre volte, invece, loggetto fornisce
funzionalità aggiuntive prende allora il nome di
Decorator - java.util.zip.CheckedOutputStream calcola un
checksum al volo e possiede un metodo aggiuntivo
per restituirlo - La libreria di classi di Java (Stream, RMI,
interfaccia grafica) utilizza pesantemente
Adaptor, Proxy e Decorator
38Conclusione
- I pattern forniscono un vocabolario comune tra i
progettisti, che facilita la comprensione di un
progetto esistente o lo sviluppo di uno nuovo - Abbiamo visto solo un piccolo insieme di pattern
- Factory, Singleton, Flyweight, State, Strategy,
Proxy, Adaptor, Decorator - I pattern migliorano le prestazioni del codice
e/o lo rendono più flessibile - Tuttavia, il codice che utilizza i pattern
potrebbe risultare più complesso del necessario
occorre quindi valutare e confrontare costi e
benefici - Svantaggio potenziale pattern possono rendere la
struttura del codice piucomplessa del
necessario di volta in volta bisogna decidere se
adottare semplici soluzioni ad hoc o riutilizzare
pattern noti - pericolo di overdesign ricordare i seguenti
motti - when in doubt, leave it out
- keep it simple
39Esercizio collezione di elementi con somma
- Si implementi il tipo collezione di elementi con
somma (SumSet). Man mano che nuovi elementi
vengono aggiunti o tolti dalla collezione viene
aggiornata la somma degli elementi - Quindi deve esistere l'operazione di somma per
gli elementi da inserire - Si utilizzi il pattern Strategy, utilizzando un
interfaccia Adder che definisce un metodo per la
somma
40Interfaccia Adder
- public interface Adder //OVERVIEW
- public Object add(Object x, Object y)
- throws ClassCastException, NullPointerException
- public Object sub(Object x, Object y)
- throws ClassCastException, NullPointerException
- public Object zero()
-
- NB interfaccia Adder non è supertipo dei tipi i
cui elementi vanno sommati - Serve, per ogni dato tipo che si voglia inserire
nellinsieme a (definire classi per) creare
oggetti con metodi per sommare o sottrarre
elementi di quel tipo - NB si paga il prezzo della maggiore flessibilità
con una maggior quantità di definizioni (un nuovo
tipo aggiuntivo per ogni tipo di oggetto da
inserire - Obiettivo (non perdiamolo di vista!) ottenere
classe SumSet polimorfa che non deve essere
modificata per inserire nuovi tipi di oggetti
41Unimplementazione di Adder PolyAdder
- public class PolyAdder implements Adder
- private Poly z // il Poly zero
- public PolyAdder() z new Poly()
- public Object add (Object x, Object y)
- throws NullPointerException,
ClassCastException - if ( x null y null) throw new
NullP. - return ((Poly) x).add((Poly) y)
- public Object sub (Object x, Object y)
- // simile ad add
- public Object zero () return z
-
- NB I metodi di PolyAdder (add e sub) sono
distinti e diversi dai metodi omonimi di Poly
signature diversa. Per inserire oggetti Integer
in SumSet occorrerebbe definire IntegerAdder
con add e sub, che Integer non possiede.
42Classe SumSet (con implementazione parziale)
- public class SumSet //OVERVIEW
- private Vector els // contiene gli elementi
- private Object sum // contiene la somma
- private Adder a //oggetto per sommare e
sottrarrre - public SumSet (Adder p) throws NullPointerExceptio
n - els new Vector() a p sum p.zero()
- public void insert (Object x) throws NullP,
ClassCastEx -
- sum a.add(sum, x)
-
- public Object getSum()return sum
43Classe SumSet (cont.)
- Ogni oggetto SumSet definito in termini
(corredato) di qualche oggetto Adder - Elementi di SumSet tutti omogenei
- ma ora tipo degli elementi determinato alla
creazione della collezione dalloggetto Adder
passato al costruttore non puo cambiare - Adder a new PolyAdder()
- SumSet s new SumSet(a)
- s.insert(new Poly(3, 7))
- s.insert(new Poly(4, 8))
- Poly p (Poly) s.sum() // p e 3x74x8
- NB loggetto SumSet s può contenere solo oggetti
Poly, perché costruito con un PolyAdder. Verifica
però fatta a run-time...