[Italian] Design Patterns: i patterns strutturali secondo la Gang Of Four

(Article copied from my old blog)
Design Patterns: Elements of Reusable Object-Oriented Software

Design Patterns: Elements of Reusable Object-Oriented Software è stata la prima raccolta comprensiva dei Design pattern per la progettazione di software utilizzando la progettazione ad oggetti all’insegna della massima flessibilità e riusabilità del codice.

I design pattern sono infatti dei pattern, delle soluzioni generiche da applicare in determinati casi, soluzioni eleganti architetturali ottimizzate all’insegna appunto della flessibilità, della riusabilità.

Il libro pubblicato nel 1995, è oggi ancora attuale (tranne forse nel linguaggio utilizzato per gli esempi, lo Smalltalk), ed è tra i più popolari libri sui design pattern (ne esistono molti). In particolare ho deciso di scrivere questa serie di articoli dopo aver letto, su un annuncio di lavoro, che veniva espressamente richiesta la conoscenza dei design pattern definiti dai GoF, ovvero i design pattern definiti in questo libro (il libro è stato scritto da quattro differenti autori, successivamente conosciuti come Gang Of Four, abbreviato in GoF).

Nel libro, i design pattern vengono suddivisi in 3 categorie:

In questo secondo articolo saranno affrontati i design pattern strutturali. Per approfondire la tua conoscenza dell’argomento, puoi acquistare il libro in inglese oppure la sua Versione in italiano.

I design pattern strutturali

I design pattern strutturali sono quei design pattern che forniscono un modo semplice per realizaare relazioni tra entità: descrivono in che modo gli oggetti possono essere combinati per creare strutture complesse.

All’interno di questo insieme di pattern si potrebbe distinguere ulteriormente tra quei pattern che riguardano la combinazione di classi, realizzata utilizzando principalmente l’ereditarietà e quei pattern che riguardano l’associazione e l’aggregazione di oggetti per formare strutture complesse.

Adapter (o Wrapper)

Permette di traslare l’interfaccia di una classe in un’altra interfaccia: in pratica realizza una compatibilità tra interfacce che nativamente non era presente.

E’ un pattern utilizzato quando una classe offre delle funzionalità che risolvono specifici problemi, ma non implementa l’interfaccia che mi serve.

Si implementa scrivendo una classe Adapter che implementi l’interfaccia desiderata e mappi tali metodi in chiamate alla classe che volevo adattare, chiamata in letteratura Adaptee. Questo mapping può avvenire in due metodi, o attraverso l’ereditarietà o attraverso la composizione.

Bridge

Disaccoppia un’interfaccia o una classe astratta dalla sua implementazione, in modo che quest’ultima sia libera di variare: è utile nel caso un’implementazione sia in comune tra molti oggetti e possa venir riutilizzata ulteriormente.

Un’ulteriore vantaggio è la possibilità di nascondere i dettagli di implementazione dal client, addirittura modificandoli a run-time.

Si implementa creando appunto un’interfaccia Abstraction che sarà implementata da una certa classe concreta. Al momento dell’istanziazione della classe concreta andrà fornito come argomento il ConcreteImplementor, la classe che contiene l’implementazione dei vari metodi. Tale argomento viene passato attraverso una interfaccia Implementor generica, implementata da tutte le versioni dei ConcreteImplementor che saranno realizzati.

Composite

Questo pattern serve a creare strutture annidate di oggetti senza dover differenziare tra foglie e nodi intermedi della struttura ad albero: essi godono infatti della medesima interfaccia Component.

E’ possibile aggiungere figli nella struttura gerarchica con le operazioni add e remove presenti: si può scegliere se inserire tali operazioni nell’interfaccia Component (gestendole in maniera appropriata laddove sono impossibili, cioè nel caso delle foglie) oppure direttamente nelle implementazioni dei nodi non foglia (Composite).

Questo pattern ha come vantaggi la possibiità appunto di gestire indipendentemente nodi e foglie, aggiungendo livelli a piacimento all’interno della struttura ad albero.

Decorator

Pattern che permette di aggiungere responsabilità addizionali, cioè funzionalità a runtime ad un certo oggetto: è una flessibile alternativa all’ereditarietà.

Permette inoltre di semplificare il codice scrivendo classi più snelle, specifiche per una determinata funzionalità: è importante che anche le interfacce siano snelle, altrimenti diventa molto complicata da implementare per tutti i Decorators.

Un altro fattore su cui fare attenzione è il peggioramento delle prestazioni quando ho molti Decorators a catena.

Dal punto di vista implementativo il design pattern prevede una interfaccia o una classe astratta che verrà implementata/estesa dai vari Decorator previsti.

Tale interfaccia/classe astratta deve implementare un certo tipo di interfaccia, che rappresenta una “classe decorabile”, che deve essere implementata anche dagli oggetti semplici che voglio decorare.

Questi oggetti “decorabili” sono forniti come argomento al decorator, che li ingloberà, tenendo un riferimento al suo interno all’oggetto decorabile.

Facade

O facciata, preved che venga esposta in un’unica interfaccia un insieme di funzionalità/interfacce offerte da un sottosistema.

Definisce una interfaccia di alto livello che rende il sottosistema più semplice da usare, può essere utilizzata ad esempio per esporre all’esterno tramite un’unica interfaccia una intera libreria.

Un altra applicazione può essere quella di combinare vari metodi in un un solo metodo, ed esporre all’esterno la funzionalità, in modo che sia possibile eseguire un task complesso e ricorrente con una singola chiamata.

Dal punto di vista dell’implementazione si tratta di una classe che espone una certa interfaccia e richiama al suo interno molti oggetti della logica di business differenti, utilizzandoli per portare a termine i suoi compiti.

Flyweight

Pattern per evitare di istanziare un grande numero di oggetti, riutilizzando istanze preesistenti per rappresentare nuovi oggetti.

Si utilizza quando c’è un grande numero di oggetti tale che questi potrebbero addirittura non entrare in memoria, oppure nel caso gli oggetti hanno uno stato calcolabile a runtime o memorizzato su disco. Il beneficio di questo pattern è appunto la riduzione del numero di oggetti creati, quindi della memoria utilizzata, e quindi un vantaggio dal punto di vista prestazionale.

Gli oggetti flyweight possono essere creati da una factory che si occupa di stabilire quando un nuovo oggetti va creato e quando invece va riutilizzato un oggetto già esistente (deve quindi contenere un indice, una struttura che memorizzi gli oggetti già istanziati).

Un esempio di applicazione è per rappresentare graficamente un carattere in un word processor: tutte le caratteristiche del carattere (font, dimensioni, altre caratteristiche di formattazione) sono salvate nell’oggetto, che in realtà si ripete più volte, e cambia solo la posizione dello stesso che viene memorizzata esternamente all’oggetto.

Proxy

E’ un design pattern strutturale che prevede l’inserimento di un oggetto intermedio tra il client e un oggetto che possiede determinate responsabilità.

Perchè inserire un oggetto intermedio (il Proxy)?

I problemi di questo pattern sono appunto prestazionali: viene comunque inserito un ulteriore livello di astrazione, ed inoltre un altro problema è dato dal fatto che devo stare attento ad utilizzare l’oggetto dietro il proxy sempre in questa modalità, utilizzando a volte con il proxy e a volte senza potrei avere comportamenti inattesi.

Esempi di applicazione: login, cache (salvo una cache sul proxy, poi quando raggiungo una certa dimensione apro l’oggetto reale e deposito là la cache).


Nel prossimo articolo saranno trattati i design pattern comportamentali.

Se desideri maggiori informazioni sui design pattern creazionali, ti suggerisco di acquistare il libro della GoF su Amazon (al miglior prezzo).