Design Patterns
Michelangelo Diligenti Ingegneria Informatica e
dell'Informazione
diligmic@dii.unisi.it
Design Patters
●
Scrivere codice è un'attività creativa
●
Tuttavia è possible fornire direttive di design generiche: spesso basate su OO
Sono detti Design Patterns
●
Raccolti in sottocategorie
Creazionali per la creazione di oggetti; es. Singleton, FactoryMethod
Comportamentali per l'interazione tra oggetti;
es.State, Command
Strutturali composizione di classi ed oggetti
es.Composite, Decorator
Proxy
●
Design pattern strutturale
●
Fornisce un surrogato dell'oggetto attraverso un'interfaccia quasi identica all'oggetto originale
●
Ma per alcune operazioni effettua operazioni aggiuntive
●
Esempi di uso
Un oggetto ha un'implementazione semplice (veloce) ed una complessa (lenta da inizializzare)
Il proxy chiama la veloce quando possibile e ritarda l'inizializzazione della lenta fino a quando serve
Smart pointers! Vediamoli
Puntatori e problemi associati
●
Puntatori sono una comune causa di 4 errori
Memory leaks
void function() { int* i = new int[10]; }
puntatori non inizializzati int* i; int j = *i * 3;
deallocazioni multiple int* p = new int; int* q = p;
delete p; delete q;
dangling pointers int* p = new int; int* q = p;
delete p;
if (p != NULL) p->DoSomething(); // p dangling!
p = NULL; // p non è dangling
if (q != NULL) q->DoSomething(); // q è ancora dangling!
i esce di scope ma la memoria puntata non è deallocata anche se non più raggiungibile
i non inizializzato, il valore di j non è definito
q e p puntano alla stessa memoria, una volta sola va deallocata
p cancellato, suo valore è indefinito se non assegnato a NULL
Smart pointers
●
Soluzione usare design patterns per evitare problemi
●
Creare classi che si usano come puntatori
ma non ne hanno i rischi
evitano che rimanga memoria non puntata
o che si deallochi due volte la stessa memoria
●
Sono dei proxy
●
Implementazioni usano templates per essere
applicabili a puntatori a qualsiasi tipo
Scoped pointer
●
Incapsula un puntatore
Usabile in modo trasparente come un puntatore tramite operator* ed operator->
●
Evita che
scoped_ptr non sono copiabili, non si trasferisce proprietà della memoria allocata (ownership)
puntatore possa non essere inizializzato
Che il puntatore non sia deallocato uscendo da un blocco
Implementa un semplice garbage collector
Scoped pointer
●
Utilizzo tipico
void function() {
scoped_ptr<int> p(new int); // si inizializza tramite il costruttore // fai qualcosa con p
} // p deallocato uscendo da blocco, no leaks!
●
Attenzione in standard c++-11 implementazione di scoped_ptr rinominata unique_ptr<T>
●
Vediamo la sua implementazione
Scoped pointer: implementazione
template <typename T> class scoped_ptr { private:
T* ptr;
T& operator=(T& altro) { return *ptr; } scoped_ptr(const scoped_ptr<T>& p) { } public:
explicit scoped_ptr(T* p = NULL) : ptr(p) {}
~scoped_ptr() { if (ptr != NULL) delete ptr;}
T& operator*() {return *ptr;}
T* operator->() {return ptr;}
void Reset(T * p = NULL) { if (ptr) delete ptr;
ptr = p;
} };
Puntatore è privato e non accessibile direttamente
Operators garantiscono che sia usabile come un puntatore dal programmatore
azzera il puntatore vecchio Costruttore inizializza sempre il puntatore interno. A NULL se non diversamente specificato
Distruttore garantisce che la memoria sia deallocata se si va out-of-scope
operator= e copy constructor
sono tenuti privati e non chiamabili
Auto pointer
●
Incapsula un puntatore
Usabile in modo trasparente come un puntatore tramite operator* ed operator->
●
Evita che
Puntatore possa non essere inizializzato
Che il puntatore non sia deallocato uscendo da un blocco
Implementa garbage collector
Più puntatori puntino alla stessa memoria, evita deallocazioni multiple o dangling pointers
Funzione aggiuntiva del scoped_ptr
auto_ptr è copiabile, permette passaggio di ownership!
Auto pointer
●
Utilizzo tipico
#include<memory>
void function() {
auto_ptr<int> p(new int); // si inizializza tramite il costruttore auto_ptr<int> q; // default constructor garantisce che q è NULL q = p; // Passaggio ownership: q punta a memoria allocata da p,
// p ora punta a NULL
} // q e p deallocati automaticamente uscendo da blocco, niente leaks!
●
Vediamo la sua implementazione, anche se già
disponibile nelle librerie standard
Auto pointer: implementazione
template <typename T> class auto_ptr { private:
T* ptr;
public:
explicit auto_ptr(T* p = 0) : ptr(p) {}
~auto_ptr() { if (ptr != NULL) delete ptr;}
T& operator*() {return *ptr;}
T* operator->() {return ptr;}
auto_ptr<T>& operator=(auto_ptr<T>& altro) { if (this != &altro) {
if (ptr != NULL) delete ptr;
ptr = altro.ptr; altro.ptr = NULL;
}
return *this;
} };
Puntatore è privato e non accessibile direttamente
Operators garantiscono che sia usabile come un puntatore dal programmatore
Operator= fa si che se si copia un puntatore il copiato venga assegnato a NULL (cosi che si evita di deallocarlo)
Costruttore inizializza sempre il puntatore interno. A NULL se non diversamente specificato
Distruttore garantisce che la memoria sia deallocata se si va out-of-scope
Smart arrays
●
auto_ptr, shared_ptr, hanno la loro versione per array
puntano non a elemento ma ad array allocato dinamicamente
implementazione come precedenti ma con delete[]
implementano operator[], usabili in modo trasparente
Si usano in modo equivalente a smart pointers
Esercizio: implementate un auto_array ed utilizzatelo
nella classe Vector implementata in precedenza
Auto pointer/array e classi
●
Anche usabile auto_ptr/auto_array come membro in classi (esempio soltanto: usate vector<float> in questo ca so!)
Senza auto_array
class Vector { private:
float* vec;
public:
Vector(int n) : vec(new float[n]) { }
~Vector() { delete[] vec;
} … };
Con auto_array
class Vector { private:
auto_array<float> vec;
public:
Vector(int n) : vec(new float[n]) { }
…
}; Distruttore non serve, ci pensa quello di auto_array
Distruttore serve, altrimenti vec resta deallocato
Shared pointer
●
Come un auto_ptr ma copiabile e con reference counting
Ogni volta che il puntatore viene copiato si incrementa un contatore
Ogni volta che un puntatore viene distrutto, si decrementa il contatore
Se il contatore va a zero, si dealloca la memoria
Massima flessibilità e niente leaks!
Implementato da librerie boost
#include <boost/shared_ptr.h>
Smart arrays
●
auto_ptr, shared_ptr, scoped_ptr hanno talvolta la loro versione per array
puntano non a elemento ma ad array allocato dinamicamente
implementazione come precedenti ma con delete[]
implementano operator[], usabili in modo trasparente
Si usa in modo equivalente a smart pointers
Smart arrays: esempio
#include <boost/shared_array.h> // non è standard, non c'è sempre
#include<iostream>
int main() {
boost::shared_array<int> vec(new int[100]);
for(int i = 0; i < 100; ++i)
std::cout << vec[i] << std::endl;
} // non serve fare delete[]
Singleton class
●
Classe di cui può esistere una sola istanza
Usare quando una classe ha inizializzazione lenta o usa molta memoria, deve esistere una sola istanza
class Singleton { private:
Singleton() { ... }
static Singleton* istanza;
public:
static Singleton* Get() { if (istanza == NULL)
istanza = new Singleton();
return istanza;
} };
Singleton* Singleton::istanza = NULL;
Costuttore privato, non è chiamabile chiamato una volta da Get()
L'istanza è statica, ne esiste
una sola. Privata, non accedibile direttamente
Permette l'accesso all'instanza.
La prima volta che è chiamato inizializza istanza via costruttore.
Istanza inizialmente è NULL
Singleton implementazione + completa
class Singleton { private:
Singleton() { ... }
static Singleton* istanza;
Singleton(const Singleton& S) { }
Singleton& operator=(const Singleton& S) { } public:
static Singleton* Get() { if (istanza == NULL)
istanza = new Singleton();
return istanza;
} … };
Singleton* Singleton::istanza = NULL;
Implementazione privata Non devono essere
chiamabili. Il Singleton non è copiabile
Metodi che definiscono
cosa fa il Singleton
Singleton: esempio
#include <string>
#include <iostream>
#include <fstream>
class Log { private:
std::ostream* os;
Log() { os = new std::ofstream(”/data/logs.txt”); } static Log* istanza;
Log(const Log& S) { }
Log& operator=(const Log& S) { return *this; } public:
static Log* Get() {
if (istanza == NULL) istanza = new Log();
return istanza;
}
void Write(const std::string& message) { (*os) << message << std::endl << std::flush; }
};
Log* Log::istanza = NULL;
classe che permette di fare registrazione eventi (logging) del sistema in modo
consistente.
Tutti i logs sono raccolti nello stesso file.
Non voglio che sia possibile sbagliare e creare più file di log
Singleton: esempio
#include <string>
#include <iostream>
#include “log.h”
int main() { ...
Log::Get()->Write(”Salvare Log”);
...
}
Chiunque può usare il log basta includere il file
con la definizione
Per usare il Log basta
prendere l'istanza e chiamare l'unico metodo disponibile Molto comodo, non serve passare Log come argomento Garanzia che una sola
istanza accede al file di log evitando problemi
Singleton come template: definizione
●
Usando templates si evita di ridefinire la struttura del Singleton ad ogni uso. Tale template va in singleton.h
template<typename T>
class Singleton { protected:
Singleton() { }
Singleton(const Singleton<T>& S) { } // protected, non copiabile
Singleton<T>& operator=(const Singleton<T>& S) { return *this; } // protected static T* istanza;
public:
static T* Get() {
if (istanza == NULL) istanza = new T();
return istanza;
} };
template<typename T> T* Singleton<T>::istanza = NULL;
Singleton come template: uso 1
#include <string>
#include <iostream>
#include “singleton.h”
class Log {
friend class Singleton<Log>;
private:
ostream* os;
Log() {
os = new std::ofstream(”/data/logs.txt”);
}
public:
void Write(const string& message) { (*os) << message << endl << flush;
} };
int main() {
Singleton<Log>::Get()->Write(”Salvare Log”);
}
Si include la definizione generica con template di un Singleton
Qualsiasi classe può essere
usata come singleton se è friend della sua versione con Singleton.
Necessario perché usa costruttore Costruttori restano privati,
chiamabili solo da Singleton Funzioni pubbliche specifiche della classe. In questo caso:
Write per scrivere sul file di log Utilizzo di Log via Singleton
Singleton come template: uso 2
class Log : public Singleton<Log> { // si usa ereditarietà da Singleton friend class Singleton<Log>;
protected:
ostream* os;
Log() { // costruttore resta protected, solo Singleton lo chiama os = new std::ofstream(”/data/logs.txt”);
}
public:
void Write(const string& message) { (*os) << message << endl << flush;
} };
int main() {
Log::Get()->Write(“Messaggio”);
Singleton<Log>::Get()->Write(“Messaggio”); // equivale al precedente }
Singleton usi tipici
●
Sistemi di logging
classe singleton deve essere visibile ovunque, non si vuole sempre passare variabile come argomento
●
Sistemi di configurazione
classe singleton deve essere visibile ovunque, non si vuole sempre passare variabile come argomento
●
Caricamento di tabelle (read-only) di grandi dimensioni
servono in vari punti del codice
non si vuole passare troppi argomenti
si vuole caricare una sola volta la tabella
Pattern creazionale: factory
●
Usato quando vi sono molte classi derivate da un unico padre
Come gestire la costruzione della classe?
Una Factory gestisce la creazione della classe
Ritorna un oggetto di classe padre
Si accede alle funzionalità specifiche della classe attraverso polimorfismo/vtable
Uno dei design patterns più usati
Evita di lasciare libera l'inizializzazione degli oggetti
Utile se gli oggetti sono complessi
Factory: esempio
●
Una classe padre e tanti figli
class Car { public:
virtual void Sposta(float km) = 0; };
class Fiat : public Car { public:
virtual void Sposta(float km); };
class Volvo : public Car { public:
virtual void Sposta(float km); };
class Ferrari : public Car { public:
virtual void Sposta(float km); };
class BMW : public Car { public:
virtual void Sposta(float km); };
Factory: esempio
class CarFactory { public:
static Car* Build(const string& tipo) { if (tipo == “fiat”) return new Fiat();
else if (tipo == “volvo”) return new Volvo();
else if (tipo == “ferrari”) return new Ferrari();
else if (tipo == “bmw”) return new BMW();
else return NULL;
};
Factory che ritorna
istanza di classe
base Car
Factory o static Factory method
●
Alternativa alla factory
Aggiungere metodo factory static nella classe padre della gerarchia
Nel caso della classe Car class Car {
...
static Car* Build(const string& tipo) { if (tipo == “fiat”) return new Fiat();
else if (tipo == “volvo”) return new Volvo();
else if (tipo == “ferrari”) return new Ferrari();
else if (tipo == “bmw”) return new BMW();
else return NULL;
Pattern creazionale: Prototype
●
Copy constructor non e' virtual, come copio oggetti parte di una gerarchia? Abbiamo puntatore al padre
●
prototype garantisce che tutti gli oggetti che derivano da un padre siano copiabili
padre P definisce un metodo Clone virtuale virtual P* Clone() const = 0;
Figlie obbligate ad implementare il metodo che ritorna una copia di se stessa
Ad esempio se una classe figlia F ha definito un copy constructor basta definire nella figlia
virtual P* Clone() const { return new F(*this);
}
Prototype: esempio
class Car { public:
virtual Car* Clone() const = 0; // macchine saranno copiabili };
class Fiat : public Car { public:
Fiat(const Fiat& fiat) { /* fai la copia */}
virtual Car* Clone() const { return new Fiat(*this);
}
};
Visitor o Iteratori
●
Dati devono essere privati per rendere sicuro l'uso della classe
Accessors permettono leggere i dati senza modificarli
●
Iteratori permettono accesso a insiemi di oggetti in sequenza
●
Permettono scansioni dall'esterno in sequenza
Iteratori permettono di scandire i dati senza esporre i dati ai cambiamenti arbitrari di un ente esterno
I contenitori STL applicano tale design pattern per
permettere accesso esterno ai suoi contenitori
Iteratori: implementazione senza operatori
class SparseVector { private:
typedef std::map<int, float> DataContainer;
DataContainer data;
public:
void Set(int i, float value) { data[i] = value; } struct Element { int i; float value; };
class Iterator { private:
DataContainer::const_iterator iter;
const SparseVector* svector;
public:
Iterator(const SparseVector& svector_) { svector = &svector_;
iter = svector->data.begin();
}
inline Element GetNext() {
Element e; e.i = iter->first; e.value = iter->second;
++iter; return e; }
inline bool HasNext() const { return (iter != svector->data.end()); } };
friend class Iterator;
Viene inizializzato su uno SparseVector
Possibile scandire
il vettore e controllare
se vi sono altri elementi
Friend della classe per
accedere ai dati privati
Iteratori: utilizzo
#include”sparse_vector.h”
#include <iostream>
int main() {
SparseVector vec;
vec.Set(2, 5.0);
vec.Set(20, 2.0);
vec.Set(5, 6.0);
SparseVector::Iterator iter(vec);
while (iter.HasNext()) {
SparseVector::Element e = iter.GetNext();
std::cout << e.i << “::“ << e.value << std::endl;
}
return 0;
}
possibile iterare senza avere
accesso agli elementi del vettore
Iteratori: esercizio
1) Fornire di un iteratore la classe vector o matrix o pila che avevamo realizzato in classe.
2) Creare una classe per la gestione dei treni di trenitalia. Un treno e' associato ad un codice, una stazione di partenza ed arrivo, e la sua posizione attuale (latitudine, longitudine). Creare una classe per la gestione dei treni che permetta di
●
aggiungere un treno;
●
stampare la posizione dei treni per codice crescente;
●
fornire la classe di un iteratore per poter iterare sui
treni.
Observer
●
Propaga le notifiche da un istanza ad un insieme di istanze correlate, garantendone la consistenza
●
Composta da due classi
Un osservante (observer)
Metodo che permette di notificargli un cambiamento
Un osservato
Osservato ha lista di osservanti
Ogni volta che vi è una modifica nell'osservato, gli osservanti sono notificati
Facile garantire che gli osservanti vedano ogni
modifica fatta sull'osservato
Observer: esempi di applicazioni
●
La borsa (stock market)
–
Un investitore (observer) possiede alcune azioni
–
Ogni titolo posseduto è osservato
–
Ogni volta che il prezzo di uno dei titoli osservati si modifica, l'osservato viene notificato
●
Social Networks
–
Ogni persona (observer) decide di quali altre persone è amica o segue
–
Per cui le persone sono allo stesso tempo osservatori ed osservati
–
Ogni volta che una persona emette un post, tutte le
persone che la osservano vengono notificate
Observer: esempio lo stock market
#include<iostream>
#include<string>
#include<list>
using namespace std;
class Stock; // l'osservato, serve solo dichiararlo per ora
class InvestorBase { // prototipo dell'osservante public:
virtual void Update(Stock* stock) = 0;
};
Observer: esempio lo stock market
...
class Stock { // classe osservata public:
Stock(const string& symbol, double price) : symbol_(symbol), price_(price) { }
void Attach(InvestorBase* investor) { investors_.push_back(investor); } // + osservatore void Detach(InvestorBase* investor) { investors_.remove(investor); } // - osservatore void Notify() { // notifica gli osservatori passandogli l'oggetto osservato
list<InvestorBase*>::iterator it = investors_.begin();
while(it != investors_.end()) { (*it)->Update(this); ++it; } }
double GetPrice() const { return price_; }
void SetPrice(double price) { price_ = price; Notify(); } // cambio stato, chiamo Notify() const string& GetSymbol() const { return symbol_; }
private:
string symbol_;
double price_;
list<InvestorBase*> investors_; // lista di Osservanti
Observer: esempio lo stock market
…
// L'osservatore
class Investor : public InvestorBase { public:
Investor(const string& name) : name_(name){}
void Update(Stock* stock) { // riceve la notifica cout << “Notifica a “ << name_ << “ => ” <<
<< stock->GetSymbol() << “ ” << stock->GetPrice() << endl;
}
private:
string name_;
};
Observer: esempio lo stock market
…
int main() { // il main
Stock c1("Ebay", 123.0);
Investor* i1 = new Investor("Billy");
c1.Attach(i1);
cout<<"Created investor Billy following Ebay\n"<<endl;
c1.SetPrice(125.0);
Investor* i2 = new Investor("Timmy");
c1.Attach(i2);
cout<<"\nCreated investor Timmy following Ebay\n"<<endl;
c1.SetPrice(145.0);
c1.Detach(i1);
cout<<"\nInvestor Billy not interested in Ebay anymore\n"<<endl;
c1.SetPrice(165.0);
delete i1;
delete i2;
return 0;
}
Observer: esercizio
Una stazione meteorologica ha dei sensori di temperatura e pressione atmosferica che aggiornano i valori attraverso un metodo (setter) apposito.
Gli utenti possono registrarsi alla stazione ed ottenere un
aggiornamento se la temperatura o la pressione cambiano piu'
di un grado rispetto alla precedente lettura ritornata agli utenti.
Composite
●
Gruppo di oggetti di una classe deve essere gestibile come un singolo oggetto della classe
–
Permette di realizzare alberi di oggetti che si comportano come istanze dello stesso oggetto contenuto
–
Sia i nodi foglia che l'albero intero visibili come
istanze della stessa gerarchia => stessa interfaccia
●
Pacchetti grafici ad esempio usano il pattern
–
Oggetti semplici come una linea od un cerchio sono stampabili tramite un metodo Print()
–
Oggetto complesso ottenibile come insieme di oggetti semplice: deve essere stampato tramite Print()
–
Ricorsivamente oggetti arbitrariamente complessi
Composite: esempio
●
Supponiamo di avere un classificatore che decide se un testo parla di sport
Classificatore è composto da altri classificatori uno per calcio, uno per tennis, ecc.
Classificatore per alcuni sport come calcio composto da altri sottoclassificatori: calcio a 5, calcio a 11, ecc.
Sia il classificatore che tutti i sotto classificatori hanno la stessa interfaccia
Design Pattern Composite è appropriato in questo caso
Utile se c'è una gerarchia di oggetti con stesso comportamento
Composto da classe foglia, classe composta
(contiene lista di foglie) che sono la stessa classe o
discendono dalla stessa classe
Composite: ownership delle sottoparti
●
Possibile implementare il composite con ownership sia interna che esterna
–
Composite ha ownership dei dati composti
● Allocazione dinamica dei dati composti
● Deallocazione avviene nel distruttore del composite
● Nota: Meno rischioso da usare
–
Composite non ha ownership dei dati composti
● Nessuna deallocazione
● Attenzione che i dati composti restino disponibili per tutta la vita del composite
–
Vedremo entrambe le implementazioni
class ClassifierBase { // classe padre comune a foglia e composite public:
virtual bool Classify(const string& text) const = 0;
};
class Classifier : public ClassifierBase { // classe foglia public:
Classifier(const vector<string>& words) : words_(words) { } virtual bool Classify(const string& text) const {
/* cerca le parole chiave in text, se ve ne sono almeno n% ritorna true */
...
}
private:
// Lista di parole chiave.
vector<string> words_;
};
Composite: esempio 1 un classificatore
Composite: classificatore con
ownership dei classificatori composti
class ClassifierComposite : public ClassifierBase { // composite private:
std::vector<ClassifierBase*> sottoclassificatori;
public:
void AddClassifier(ClassifierBase* c) { sottoclassificatori.push_back(c);
}
virtual bool Classify(const string& text) const { for(int i = 0; i < sottoclassificatori.size(); ++i) if (sottoclassificatori[i]->Classifiy(text))
return true;
return false;
}
~ClassifierComposite() {
for(int i = 0; i < sottoclassificatori.size(); ++i) delete sottoclassificatori[i];
}
lista di foglie lista di foglie chiamate in sequenza per realizzare il comportamento composto:
OR tra le classificazioni.
La logica e' specifica
del problema da risolvere
Se ownership delle sottoparti e' interna, il composite
dealloca le sue sottoparti. Se Composite non ha ownership, il distruttore va tolto.
{
// Creo classificatore composto per calcio
ClassifierComposite* calcio = new ClassifierComposite;
// calcio_a_5 owned da calcio
Calcio->AddClassifier(new Classifier(words_calcio_a_5));
// calcio_a_7 owned da calcio
Calcio->AddClassifier(new Classifier(words_calcio_a_7));
// calcio_a_11 owned da calcio
Calcio->AddClassifier(new Classifier(words_calcio_a_11));
// Creo classificatore composto per sport ClassifierComposite sport;
sport.AddClassifier(calcio); // calcio owned da sport
sport.AddClassifier(new Classifier(words_tennis)); // tennis owned da sport
// Classificatore composto usato da interfaccia di base class: e' un classificatore cout << sport.Classify(“Il giocatore effettua una rovesciata”) << endl;
} // qui chiamato distruttore di sport che dealloca le sottoparti
Composite: uso del classificatore
composto con ownership delle sottoparti
sport
tennis calcio
calcio a 5 calcio a11 calcio a 7
{
// Creo classificatori foglia per vari tipi di calcio Classifier calcio_a_5(words_calcio_a_5);
Classifier calcio_a_7(words_calcio_a_7);
Classifier calcio_a_11(words_calcio_a_11);
// Creo classificatore composto per calcio ClassifierComposite calcio;
calcio.AddClassifier(&calcio_a_5);
calcio.AddClassifier(&calcio_a_7);
calcio.AddClassifier(&calcio_a_11);
// Classificatore foglia per tennis Classifier tennis(words_tennis);
// Creo classificatore composto per sport ClassifierComposite sport;
sport.AddClassifier(&tennis);
sport.AddClassifier(&calcio);
// Classificatore composto usato da interfaccia di base class: e' un classificatore cout << sport.Classify(“Il giocatore effettua una rovesciata”) << endl;
Composite: uso composite senza ownership dei classificatori composti
sport
tennis calcio
calcio a 5 calcio a11 calcio a 7
Composite: esercizio 1
●
Una bicicletta si compone di componenti più semplici
–
Che a loro volta sono composti da altri componenti piu' semplici ad esempio: ruote composte da ruota anteriore e posteriore, cambio composto da cambio anteriore e posteriore, telaio, 4 rifrangenti, ecc.
●
Ogni componente ha un prezzo e la classe bicicletta ha un costo pari alla somma dei singoli componenti
●
Realizzare una bicicletta attraverso il design pattern composite
–
Deve essere possibile sapere il prezzo della bici
composta
Composite: esercizio 2
●
Realizzare un software per il plotting grafico vettoriale che usi il design pattern composite
– Ogni oggetto implementa il metodo Print(Matrice*)
– Implementare gli oggetti Segmento e Cerchio
– Implementare un oggetto Rettangolo come insieme di segmenti
– Oggetto composto Plot contiene vari oggetti Segmento o Cerchio e li stampa nel Print
– Possibile creare librerie di oggetti via via piu' complessi:
esempio oggetto Omino come insieme di oggetti semplici:
cerchio(testa)+segmento(collo) + rettangolo(torso) + 4segmenti (braccia e gambe)
●
Implementare i metodi Print per gli oggetti
– assumendo che lo schermo sia una matrice N x M, realizzare la stampa degli oggetti stampando la matrice riga a riga
Decorator
●
Decorator è una classe derivata che contiene (per composizione) un'altra istanza con stesso padre
–
L'istanza interna viene inizializzata dal costruttore
–
Classe interna ed esterna hanno la stessa interfaccia: realizza un wrapper
–
Per ogni metodo dell'interfaccia, l'oggetto interno viene invocato ricorsivamente dopo aver aggiunto un'altra funzionalità
–
Alternativa al subclassing!
Subclassing è a compile-time
Decorator può decidere cosa e come estendere l'oggetto a run-time
Decorator ha maggiore flessibilità visto che il modo di comporre funzionalità può avere molte combinazioni
Decorator: esempio
●
Scrittura su file, può essere
plain, buffered, compressa (zip), criptata tramite codifica
–
od un insieme delle precedenti buffered+compressa o buffered+criptata o compressa+criptata o
buffered+compressa+criptata
8 combinazioni possibili
Decorator: esempio
●
Scrittura su file: Soluzione 1
realizzare una classe di scrittura padre che fornisce interfaccia
subclassing fino ad ottenere tutte le predecenti combinazioni => molto oneroso
Implemento 4 figlie Plain, Buffered, Criptato o Compresso
Le figlie hanno altre classi figlie che compongono le scritture
Esempio Buffered ha come figlie BufferedCriptato e BufferedCompresso, ecc.
Decorator: esempio
●
Scrittura su file: Soluzione 2: Decorator
realizzare una classe di scrittura padre che fornisce interfaccia
Realizzare le classi figlie che contengono un'istanza di classe padre per composizione
comporre a run-time le opzioni per ottenere il Comportamento desiderato
Usare gli oggetti sfruttando l'interfaccia comune
Vediamo nella slide successiva come fare
Decorator: esempio per scrittura files
class WriterPlain { // classe base, data una stringa la stampa sul file public:
virtual bool Write(const string& str, const string& file);
};
bool WriterPlain::Write(const string& str, const string& file) { ofstream of(file.c_str());
if (!of.good()) { … } of << str;
}
Decorator: esempio per scrittura files
class WriteEncoded : public WriterPlain { // write encoded private:
WriterPlain* writer; // istanza generica di classe padre protected:
string Encode(const string& str) { … } // metodo protected che effettua encrypting public:
// Costruttore setta l'istanza interna con stessa classe padre WriteEncoded(WriterPlain* writer_) : writer(writer_) { }
bool Write(const string& str, const string& file) { // implementa interfaccia // Chiamo il Write specifico dell'oggetto interno su stringa Encoded.
writer->Write(Encode(str), file);
} };
Decorator: esempio scrittura su file
class WriteZipped : public WriterPlain { // write compresso private:
WriterPlain* writer;
protected:
// metodo protected che effettua la compressione string Zip(const string& str) { … }
public:
// Costruttore setta l'istanza interna con stessa classe padre WriteZipped(WriterPlain* writer_) : writer(writer_) { }
bool Write(const string& str, const string& file) { // implementa interfaccia // Chiamo il Write specifico dell'oggetto interno su stringa compressa.
writer->Write(Zip(str), file);
} };
Decorator: esempio
// Per scrivere plain WriterPlain writer;
writer.Write(“ciao”, “./a”);
// Per scivere compresso WriteZipped zwriter(&writer);
zwriter.Write(“ciao”, “./a”);
// per scrivere codificato
WriteEncoded ewrite(&writer);
ewriter.Write(“ciao”, “./a”);
// per scrivere codificato e compresso WriteEncoded ezwrite(&zwriter);
ezwriter.Write(“ciao”, “./a”);
// ecc.
Facade
●
Fornisce un'interfaccia unificata a varie classi (sottosistemi)
●
Permette di mantenere un'utilizzo semplice di un sistema complesso
Facade/Mortgage
Bank
Loan Bank
Credit
Customer
Facade: esempio
#include<iostream>
#include<string>
using namespace std;
class Customer { public:
Customer (const string& name) : name_(name){ } const string& Name() const { return name_; }
private:
Customer();
string name_;
};
// Sottosistema class Bank { public:
bool HasSufficientSavings(const Customer& c, int amount) { cout << "Check bank for " <<c.Name()<<endl;
return true;
}
Facade: esempio
// Sottosistema class Credit { public:
bool HasGoodCredit(const Customer& c, int amount) { cout << "Check credit for " <<c.Name()<<endl;
return true;
} };
// Sottosistema class Loan { public:
bool HasGoodCredit(const Customer& c, int amount) { cout << "Check loans for " <<c.Name()<<endl;
return true;
} };
Facade: esempio
// The 'Facade' class class Mortgage {
public:
bool IsEligible(const Customer& cust, int amount) {
cout << cust.Name() << " applies for a loan for $" << amount <<endl;
bool eligible = true;
eligible = bank_.HasSufficientSavings(cust, amount);
if(eligible) eligible = loan_.HasGoodCredit(cust, amount);
if(eligible) eligible = credit_.HasGoodCredit(cust, amount);
return eligible;
}
private:
Bank bank_;
Loan loan_;
Credit credit_;