Introduzione ai design pattern

43
1 Introduzione ai design pattern

description

Introduzione ai design pattern. Cosa 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 - PowerPoint PPT Presentation

Transcript of Introduzione ai design pattern

Page 1: Introduzione ai design pattern

1

Introduzione ai design pattern

Page 2: Introduzione ai design pattern

2

Cosa 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

Page 3: Introduzione ai design pattern

3

Design 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

Page 4: Introduzione ai design pattern

4

Abstract 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

Page 5: Introduzione ai design pattern

5

Poly 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.//@ 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)

Poly

DensePoly SparsePoly

Page 6: Introduzione ai design pattern

6

Creazione 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 un’implementazione con un’altra. 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!

Page 7: Introduzione ai design pattern

7

Factory 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 l’oggetto

iteratore associato a un contenitore (nella nomenclatura Liskov, oggetti generatori): e` un esemplare di una classe che implementa l’interfaccia 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.

Page 8: Introduzione ai design pattern

8

Factory (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 axn+b 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 < a.length; n++) if (a[n] != 0){ numCoeffs++; degree = n; } if ((numCoeffs == 2 && a[0] != 0) || numCoeffs == 1) return new SparsePoly (degree, a[degree], a[0]); return new DensePoly (degree, a);}

Page 9: Introduzione ai design pattern

9

Alternativa: Factory Class

• A volte e' preferibile che il metodo statico sia in una classe a parte

• Es. public class FabbricaDiPolypublic 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

Page 10: Introduzione ai design pattern

10

Abstract 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

Page 11: Introduzione ai design pattern

11

Abstract Factory descritto in UML

Page 12: Introduzione ai design pattern

12

Pattern per Ottimizzazioni comuni

• Alcuni pattern forniscono “trucchi” semplici e funzionali per velocizzare un programma o ridurne i requisiti di memoria.

• A volte l’utilizzo 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

Page 13: Introduzione ai design pattern

13

Singleton• 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 l’accesso alla

sola copia dell’oggetto

Page 14: Introduzione ai design pattern

14

Singleton 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 alreadyif (s == null) s = new SingletonClass();return s;

}

private SingletonClass() { … } // the constructor

// other methods

}

Page 15: Introduzione ai design pattern

15

Flyweight

• 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

Page 16: Introduzione ai design pattern

16

Flyweight: 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 l’oggetto richiesto esiste già nella tabella prima di crearlo; se non esiste, chiama un costruttore (privato!), altrimenti restituisce un reference all’oggetto 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.

Page 17: Introduzione ai design pattern

17

UML per Flyweight

Page 18: Introduzione ai design pattern

18

Esempio 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}

Page 19: Introduzione ai design pattern

19

State• 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 l’implementazione– 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 dell’oggetto, possono essere utilizzate diverse implementazioni senza che l’utente se ne accorga

Page 20: Introduzione ai design pattern

20

State (2)Implementazione del pattern• Si crea un’interfaccia o una classe astratta che

rappresenta le parti dell’oggetto che possono essere sostituite nel corso della vita dell’oggetto

• Ciascuna delle possibili rappresentazioni (stati) diventa un’implementazione dell’interfaccia o un erede della classe astratta

• La classe principale conterrà il codice per scegliere la rappresentazione più adatta e per delegare l’implementazione alla sottoclasse piu`appropriata per lo stato dell’oggetto

Page 21: Introduzione ai design pattern

21

Esempio di StateClasse BoolSet, analogo dell’Intset : un insieme di boolean

che cambia implementazione a seconda del numero di elementi: si usano due classi SmallBoolSet e BigBoolSet a seconda della cardinalità dell’insieme

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); }}

Page 22: Introduzione ai design pattern

22

Esempio 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 < 0) throw new ArrayIndexOutOfBoundsException(n); return n < MAX_SIZE && (bitset & (1 << n)) != 0; }

Page 23: Introduzione ai design pattern

23

Esempio 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 < 0) throw new ArrayIndexOutOfBoundsException(n); if (val) { if (n >= MAX_SIZE) return new BigBoolSet (this).set (n, val);

bitset |= (1 << n); } else if (n < MAX_SIZE) bitset &= ~(1 << n); return this; } }

Page 24: Introduzione ai design pattern

24

Esempio di State (4)Per la classe BigBoolSet vediamo solo il metodo checostruisce un BigBoolSet a partire da uno SmallBoolSet:

class BigBoolSet implements BoolSetState { ... public BigBoolSet (SmallBoolSet s) { for (i = 0; i < s.MAX_SIZE; i++) if (s.get (i)) set (i, true); } ...}

Page 25: Introduzione ai design pattern

25

Procedure 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)

• L’unico modo di ottenere questo risultato è definire classi o interfacce molto piccole.Ci sono esempi nella libreria di classi di Java– Comparable– Runnable– ActionListener

Page 26: Introduzione ai design pattern

26

Strategy• Il pattern Strategy fornisce un oggetto che

compie un’operazione precisa, richiesta dall’esterno– Per esempio, stabilire un ordinamento tra oggetti

• L’operazione è esprimibile con clausole Requires e Ensures

• Un esempio di questo pattern nell’interfaccia Comparator di JDK 1.4

Page 27: Introduzione ai design pattern

27

UML

Page 28: Introduzione ai design pattern

28

Esempio 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 comepublic static void sort(Object []s…//@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, l’oggetto 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

Page 29: Introduzione ai design pattern

29

Interface Comparatorinterface Comparator { //OVERVIEW: immutabile … … …public int compare (Object o1, Object o2)

throws ClassCastException, NullPointerException;/*@ensures (* se o1 e o2 non sono di tipi confrontabili@ lancia ClassCastException @ altrimenti: o1<o2 ret –1 @ o1==o2 ret 0 @ o1>o2 ret 1 }

NB: interfaccia non è supertipo dei tipi i cui elementi vanno comparati!

Page 30: Introduzione ai design pattern

30

metodo 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 String[30]; ... Java.util.Arrays.sort(s, new AlphabeticComparator()); ...

Page 31: Introduzione ai design pattern

31

“adattare” 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”– l’oggetto visibile all’ esterno si chiama oggetto esterno

Page 32: Introduzione ai design pattern

32

Adaptor• La strategia delineata nella slide precedente

prende il nome di Adaptor quando l’interfaccia dell’oggetto interno è diversa da quella dell’oggetto esterno

• L’oggetto esterno e’ l’Adapter, quello interno l’Adaptee.– le librerie di classi per l’interfaccia 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 un’interfaccia Java

Page 33: Introduzione ai design pattern

33

UML

Page 34: Introduzione ai design pattern

34

Proxy

• Quando l’oggetto interposto espone esattamente la stessa interfaccia dell’oggetto separato, di cui fa le veci, esso prende il nome di Proxy– java.util.zip.DeflaterOutputStream comprime

automaticamente i dati scritti• Scopo del Proxy:posporre o addirittura evitare

l‘istanziazione 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

Page 35: Introduzione ai design pattern

35

UML

Page 36: Introduzione ai design pattern

36

Documentazione UML del pattern Proxy

: Client : Proxy : Server

1: request( )2: preProcess( )

3:

4: request( )

5:

6: postProcess( )

7:

Some private processing operations

Page 37: Introduzione ai design pattern

37

Decorator• Altre volte, invece, l’oggetto 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

Page 38: Introduzione ai design pattern

38

Conclusione• 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

piu`complessa 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”

Page 39: Introduzione ai design pattern

39

Esercizio: 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

Page 40: Introduzione ai design pattern

40

Interfaccia Adderpublic 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 nell’insieme 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

Page 41: Introduzione ai design pattern

41

Un’implementazione di Adder: PolyAdder

public class PolyAdder implements Adder {private Poly z: // il Poly zeropublic 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.

Page 42: Introduzione ai design pattern

42

Classe SumSet (con implementazione parziale)public class SumSet{ //OVERVIEW … … …

private Vector els; // contiene gli elementiprivate Object sum; // contiene la sommaprivate Adder a; //oggetto per sommare e sottrarrre

public SumSet (Adder p) throws NullPointerException{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;}

}

Page 43: Introduzione ai design pattern

43

Classe 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 dall’oggetto 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` 3x^7+4x^8

• NB: l’oggetto SumSet s può contenere solo oggetti Poly, perché costruito con un PolyAdder. Verifica però fatta a run-time...