Vai alla Home Page About me Courseware Federica Living Library Federica Federica Podstudio Virtual Campus 3D Le Miniguide all'orientamento Gli eBook di Federica La Corte in Rete
 
I corsi di Ingegneria
 
Il Corso Le lezioni del Corso La Cattedra
 
Materiali di approfondimento Risorse Web Il Podcast di questa lezione

Stefano Russo » 15.Design pattern strutturali. Esempi


Pattern strutturali

I pattern strutturali sono relativi a come classi e oggetti sono composti per formare strutture più grandi.

I design pattern strutturali basati su classi utilizzano l’ereditarietà per generare classi che combinano le proprietà di classi base.

I design pattern strutturali basati su oggetti mostrano come comporre oggetti per realizzare nuove funzionalità. Danno flessibilità alla composizione che viene modificata a run-time, cosa impossibile con la composizione statica (con classi).

Design pattern strutturali

Alcuni structural patterns catalogati:

  • Adapter: unisce le interfacce di differenti classi;
  • Bridge:  separa l’interfaccia di un oggetto dalla sua implementazione;
  • Composite:  una struttura ad albero per rappresentare oggetti complessi;
  • Decorator: aggiunge funzionalità (responsabilità) ad oggetti dinamicamente;
  • Façade:  una singola classe che rappresenta un’intero sistema;
  • Proxy: un oggetto che rappresenta un altro oggetto.

Design pattern strutturali

Esempi di structural patterns illustrati in dettaglio nel seguito:

  • Façade;
  • Adapter;
  • Decorator.

Façade – Scopo

Rendere più semplice l’uso di un (sotto)sistema.

Fornire un’unica interfaccia per un insieme di funzionalità “sparse” su più interfacce/classi.


Façade – Motivazione

La suddivisione di un sistema in sottosistemi aiuta a ridurre la complessità.

Tuttavia, occorre diminuire comunicazione e dipendenze tra i sottosistemi.

L’utilizzo di un oggetto façade fornisce un’unica e semplice interfaccia alle funzionalità del sottosistema.

Façade – Motivazione

Robot con quattro classi:

  1. Camera (per identificare gli oggetti)
  2. Arm (mobile)
  3. Pliers (per afferrare)

Façade – Motivazione

Quanto deve conoscere della meccanica l’operatore?
Si supponga di voler indivduare un oggetto e spostarlo in una locazione predefinita:
oldLocation = Camera.identify(object);
Arm.move(oldLocation);
Pliers.close();
Arm.move(newLocation);
Pliers.open();

Problema: non c’è incapsulamento: l’operatore deve conoscere la struttura ed il comportamento.

Façade – Motivazione


Façade – Applicabilità

Utilizzare il pattern Façade:

  • per fornire una vista semplice e di default di un sottosistema complesso;
  • nel caso di sottosistema stratificato, è possibile utilizzare façade come entry point per ciascun livello.

Façade – Struttura


Façade – Partecipanti

Façade:

  • conosce quali classi di un sottosistema sono responsabili per una richiesta;
  • delega le richieste del client agli oggetti appropriati del sottosistema.

Classi del sottosistema:

  • implementano le funzionalità del sottosistema;
  • gestiscono il lavoro assegnato da Façade;
  • non hanno alcun riferimento della facciata.

Façade – Esempi

Classi per disegno 3D per disegnare in 2D.

Compilatore:

  • Classi Parser, Scanner, Token, SyntacticTree, CodeGenerator, ecc….;
  • Classe Compiler con metodo compile().

Façade – Conseguenze

Promuove un accoppiamento debole fra cliente e sottosistema.

Nasconde al cliente le componenti del sottosistema.

Il cliente può comunque, se necessario, usare direttamente le classi del sottosistema.

Façade – Implementazione

L’accoppiamento tra i client ed il sottosistema può essere ridotto anche rendendo Façade una classe astratta con sottoclassi concrete per diverse implementazioni di un sottosistema.

In tal caso, i client possono comunicare con il sottosistema attraveso l’interfaccia della classe astratta Façade.

Façade – Esempio


Façade – Esempio

Il sottosistema di compilazione definisce una classe BytecodeStream che implementa un flusso di oggetti Bytecode. Ciascun oggetto Bytecode incapsula un bytecode che specifica le istruzioni macchina.
La classe Scanner del sottosistema riceve un flusso di caratteri e genera un flusso di token, un token per volta.
Il Parser effettua l’analisi dei token generati dallo Scanner e genera un parse tree utilizzando un ProgramNodeBuilder.
Il parse tree è fatto di istanze di sottoclassi di ProgramNode.
ProgramNode utilizza un oggetto CodeGenerator per generare codice macchina nella forma di oggetti Bytecode in un flusso BytecodeStream.
La classe Compiler è una “facciata” che mette tutti I pezzi insieme fornendo una semplice interfaccia per la compilazione del codice sorgente e la generazione del codice per una particolare macchina.

Façade – Esempio

Mostra codice

Façade – Esempio

Mostra codice

Façade – Esempio

Il metodo Traverse genera il codice macchina: Mostra codice

Façade – Esempio

Mostra codice

Façade – Esempio

Ogni sottoclasse di ProgramNode (StatementNode, ExpressionNode, …) implementa il metodo Traverse da richiamare sui propri oggetti figli: Mostra codice

Façade – Esempio

Mostra codice

Adapter – Scopo

Adattare l’interfaccia di una classe già pronta all’interfaccia che il cliente si aspetta.

“Adapter” come l’”adattatore” per prese di corrente.

Adapter – Motivazione

Talvolta una classe progettata per il riuso non è riusabile solo perché la sua interfaccia non coincide con quella del dominio specifico di un’applicazione.

Adapter – Motivazione

Si consideri un editor di disegno che fornisce l’interfaccia Figura i cui metodi riempi e display sono implementati dalle classi Punto, linea e Quadrato.


Adapter – Motivazione

Vogliamo aggiungere Cerchio

Lo implementiamo da zero?

  • Fatica inutile…

Usiamo la classe Circle?

  • Ha un’interfaccia diversa che non possiamo modificare.

Creiamo un adattatore.


Adapter – Motivazione


Adapter – Applicabilità

Usiamo Adapter quando:

  • vogliamo utilizzare una classe esistente la cui interfaccia non risponde alle nostre necessità;
  • si vuole realizzare una classe riusabile che coopera con classi che non necessariamente hanno un’interfaccia compatibile;
  • occorre utilizzare sottoclassi esistenti le cui interfacce non possono essere adattate sottoclassando ciascuna di esse. Un object adapter può adattare l’interfaccia della classe base.

Due tipi di Adapter

Object Adapter

  • basato su delega/composizione.

Class Adapter

  • basato su ereditarietà;
  • l’adattatore eredita sia dall’interfaccia attesa sia dalla classe adattata;
  • no eredità multipla: l’interfaccia attesa deve essere un’interfaccia, non una classe.

Adapter – Struttura


Adapter – Struttura


Adapter – Partecipanti

Target

  • Definisce l’interfaccia specifica che il client utilizza.

Client

  • Collabora con gli oggetti conformi all’interfaccia Target.

Adaptee

  • L’interfaccia esistente che deve essere adattata.

Adapter

  • Adatta l’interfaccia di Adaptee all’interfaccia Target.

Adapter – Conseguenze

Class Adapter:

  • non va bene se vogliamo adattare anche le sottoclassi;
  • adapter potrebbe sovrascrivere dei comportamenti di Adaptee;

Object Adapter:

  • un singolo Adapter funziona con gli oggetti Adaptee e quelli delle sottoclassi;
  • é difficile sovrascrivere il comportamento di Adaptee.

In alcuni linguaggi (Smalltalk) è possibile realizzare classi che includono un adattamento dell’interfaccia (pluggable adapter).

Se due client richiedono di vedere un oggetto in maniera diversa è possibile definire dei two-way adapters.

Adapter – Implementazione

In C++, un class adapter può essere implementato facendo ereditare ad Adapter pubblicamente da Target e privatamente da Adaptee: in tal modo Adapter è un sottotipo di Target ma non di Adaptee.

Adapter – Esempio

Vediamo come implementare il diagramma sottostante sia per Class Adapter che per Object Adapter.

Shape e TextView sono uguali, ovviamente, in entrambi i casi.


Adapter – Esempio

Mostra codice

Adapter – Esempio

Class Adapter
Mostra codice

Adapter – Esempio

La funzione BoundingBox converte l’interfaccia di TextView per renderla conforme a quella di Shape.

Mostra codice

Adapter – Esempio

bool TextShape::IsEmpty () const {
return TextView::IsEmpty();
}

Si supponga che esista una classe TextManipulator per la manipolazione di un TextShape.

Manipulator* TextShape::CreateManipulator () const {
return new TextManipulator(this);
}

Adapter – Esempio

Object Adapter

Utilizza la composizione di oggetti per combinare classi con interfacce diverse.

In questo approccio l’adattatore TextShape mantiene un puntatore a TextView.

Adapter – Esempio

Mostra codice

Adapter – Esempio

TextShape deve inizializzare il puntatore all’istanza di TextView.

Mostra codice

Adapter – Esempio

bool TextShape::IsEmpty () const {
return _text->IsEmpty();
}

CreateManipulator è uguale al caso del Class Adapter.

Facade vs. Adapter

Entrambi sono “wrapper” (involucri).

Entrambi si basano su un’interfaccia, ma:

  • Façade la semplifica;
  • Adapter la converte.

Decorator – Scopo

Aggiungere responsabilità ad un oggetto dinamicamente.

Alternativa (più flessibile) alla creazione di sottoclassi per l’estensione di funzionalità (anche la specializzazione può aggiungere responsabilità/funzionalità/operazioni).

Decorator – Motivazione

Talvolta occorre aggiungere responsabilità a dei singoli oggetti, non ad un intera classe.

Si consideri ad es. l’interfaccia grafica di un programma di videoscrittura, alla quale si vuole aggiungere una barra di scorrimento e un bordo di contorno.

Decorator – Motivazione


Decorator – Motivazione

Ereditarietà
Contro

  • AreaDiTestoConBordoEBarraScorrimento? AreaDiTestoConBarraScorrimentoEBordo? Altre 2 sottoclassi?
  • Statico: non posso aggiungere funzionalità dopo la creazione.

Decorator – Motivazione

Racchiudere l’oggetto da “decorare” (l’area di testo) in un altro oggetto, responsabile di gestire la “decorazione” (il bordo, la scrollbar): il “decoratore”.

Il decoratore trasferisce/delega e fa qualcosa in più (prima o dopo).

Il decoratore ha l’interfaccia conforme all’oggetto decorato, e quindi è trasparente al cliente.


Decorator – Motivazione


Decorator vs. ereditarietà

Decorator

  • Dinamico (run time)
  • Senza vincoli per il cliente (può inventarsi combinazioni non previste)
  • Aggiunge responsabilità a un singolo oggetto
  • Evita esplosione combinatoria

Ereditarietà

  • Statico (compile time)
  • Con vincoli per il cliente (non può inventarsi combinazioni non previste)
  • Aggiunge responsabilità a (tutte le istanze di) una classe
  • Può causare esplosione combinatoria

Decorator – Applicabilità

Usiamo il Decorator quando:

  • occorre aggiungere responsabilità a singoli oggetti dinamicamente ed in maniera trasparente, cioè non influenzando tutti gli altri oggetti;
  • vogliamo aggiungere responsabilità che in seguito possono essere eliminate;
  • quando non è possibile utilizzare l’ereditarietà.

Decorator – Applicabilità

Usi tipici

  • Stream Java
  • Interfacce utente grafiche

Componente grafica: area di testo, finestra, panel, …

decorata con: bordo, barra di scorrimento, … (eventualmente più di uno!)

Decorator – Struttura


Decorator – Partecipanti

Component (VisualComponent)

  • Definisce l’interfaccia degli oggetti ai quali possono essere aggiunte responsabilità dinamicamente.

ConcreteComponent (TextView)

  • Definisce un oggetto al quale possono essere aggiunte responsabilità dinamicamente.

Decorator

  • Ha un riferimento all’oggetto Component e definisce un’interfaccia conforme a quella di Component.

ConcreteDecorator (BorderDecorator, ScrollDecorator)

  • Aggiunge responsabilità al component.

Decorator – Conseguenze

Vantaggi;

  • più flessibile dell’ereditarietà statica;
  • evita classi più in alto della gerarchia di assumersi eccessive responsabilità offrendo un approccio di tipo pay-as-you-go.

Svantaggi:

  • un componente decorato è diverso da un componente non decorato;
  • genera un sistema composto di tanti piccoli oggetti simili.

Decorator – Implementazione

L’interfaccia del Decorator deve essere conforme a quella del Component che decora.

L’interfaccia di Component deve essere leggera.

Nel caso in cui si voglia aggiungere un’unica responsabilità aggiuntiva non è necessario usare la classe astratta Decorator.

Decorator – Esempio

Mostra codice

Decorator – Esempio

Mostra codice Mostra codice

Decorator – Esempio

Mostra codice

Decorator – Esempio

Si consideri ora un oggetto finestra nel quale poniamo il nostro component:

void Window::SetContents (VisualComponent* contents) {
// ...
}

Per cui possiamo scrivere:

Window* window = new Window;
TextView* textView = new TextView;
window->SetContents(textView);

Decorator – Esempio

Per avere una TextView con bordo e scrollbar:

window->SetContents(
new BorderDecorator(
new ScrollDecorator(textView), 1
)
);

  • Contenuti protetti da Creative Commons
  • Feed RSS
  • Condividi su FriendFeed
  • Condividi su Facebook
  • Segnala su Twitter
  • Condividi su LinkedIn
Progetto "Campus Virtuale" dell'Università degli Studi di Napoli Federico II, realizzato con il cofinanziamento dell'Unione europea. Asse V - Società dell'informazione - Obiettivo Operativo 5.1 e-Government ed e-Inclusion