• Non ci sono risultati.

Design Patterns

N/A
N/A
Protected

Academic year: 2021

Condividi "Design Patterns"

Copied!
63
0
0

Testo completo

(1)

Design Patterns

Michelangelo Diligenti Ingegneria Informatica e

dell'Informazione

diligmic@dii.unisi.it

(2)

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

(3)

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

(4)

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

(5)

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

(6)

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

(7)

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

(8)

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

(9)

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!

(10)

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

(11)

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

(12)

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

(13)

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

(14)

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>

(15)

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

(16)

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[]

(17)

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

(18)

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

(19)

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

(20)

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

(21)

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;

(22)

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

(23)

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 }

(24)

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

(25)

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

(26)

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

(27)

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

(28)

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;

(29)

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

}

(30)

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

}

};

(31)

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

(32)

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

(33)

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

(34)

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.

(35)

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

(36)

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

(37)

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;

};

(38)

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

(39)

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_;

};

(40)

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;

}

(41)

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.

(42)

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

(43)

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

(44)

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

(45)

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

(46)

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.

(47)

{

// 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

(48)

{

// 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

(49)

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

(50)

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

(51)

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

(52)

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

(53)

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.

(54)

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

(55)

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;

}

(56)

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

} };

(57)

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

} };

(58)

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.

(59)

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

(60)

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;

}

(61)

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;

} };

(62)

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_;

I dettagli dei sottosistemi Bank, Loan, Credit

sono nascosti.

(63)

Facade: esempio

// The Main method int main() {

Mortgage mortgage;

Customer customer("Carlo Mazzini");

bool eligible = mortgage.IsEligible(customer, 1500000);

cout << customer.Name() << " => "

<< (eligible ? “” : “Non ”) << “Approvato” << endl;

return 0;

}

Riferimenti

Documenti correlati

Tassi età specifici di incidenza..

CISQ – Politecnico di Bari - Corso di formazione per RSPP – Modulo 6: Esempio di un insieme PED (ing. Boenzi).. Bari, 3 febbraio

Nei pazienti con carcinoma epidermoide della testa e del collo, la prognosi è influenzata dalla sede dei linfonodi coinvolti da metastasi; la sopravvivenza è infatti

Platinum based CRT remain the standard in locally advanced disease also in good prognosis pts. RT+CET is an alternative option in platinum

Larynx preservation is currently the only widely accepted setting for patients with resectable LA SCCHN in which IC has consensus value. In LA-NPC CCRT is the treatment of choice; IC

Riferimento: Linee Guida AIOM 2009 Livello di evidenza Ia Forza di.

Neoplasie della testa e del collo e trattamenti combinati.. “2 nd Young Sicilian Oncologists Day: Linee Guida AIOM, Appropriatezza e Medicina di

The magnitude of OS benefit of nivo greater in PD-L1 positive Increasing PD-L1 expression did not result in further OS benefit. Nivolumab effective regardless of