domenica 30 dicembre 2012

Design Pattern - Il cluster dei pattern GoF - [1]

Tra i vari design pattern noti in letteratura, i pattern GoF (Gang of Four) formano senza dubbio un cluster fondamentale. Conoscere i nomi e le motivazioni di questi pattern rappresenta senza dubbio un buon punto di partenza per poter successivamente approfondire i dettagli che li riguardano ed eventualmente valutarne l’utilizzo.
I 23 pattern che compongono questo cluster sono organizzati in tre categorie distinte e tra loro complementari:
  • pattern creazionali, che riguardano la creazione di istanze;
  • pattern strutturali, che si riferiscono alla composizione di classi e oggetti;
  • pattern comportamentali, che si occupano delle modalità con cui classi e oggetti interagiscono tra loro in relazione alle loro diverse responsabilità.
I pattern creazionali sono cinque:
  • Abstract Factory: fornisce un’interfaccia per creare famiglie di oggetti correlati o dipendenti senza specificare le classi concrete;
  • Builder: separa la costruzione di un oggetto complesso dalla sua rappresentazione, in modo tale che lo stesso processo di costruzione possa creare rappresentazioni differenti;
  • Factory Method: definisce un’interfaccia per creare un oggetto, ma lascia alle classi derivate di decidere quale classe istanziare. Questo pattern permette a una classe di delegare la creazione di un’istanza alle sue classi derivate;
  • Prototype: specifica il tipo degli oggetti da creare usando un’istanza prototipale e crea i nuovi oggetti a partire da questo prototipo;
  • Singleton: assicura che una classe abbia solamente un’unica istanza e fornisce un entry-point globale ad essa.
I pattern strutturali sono sette:
  • Adapter: converte l’interfaccia di una classe in un’altra interfaccia compatibile con il client. Questo pattern consente a classi diverse di collaborare tra loro, cosa che non sarebbe possibile diversamente a causa della incompatibilità delle rispettive interfacce;
  • Bridge: disaccoppia un’astrazione dalla sua implementazione affinché entrambe possano variare in modo indipendente;
  • Composite: compone una serie di oggetti in una struttura ad albero secondo una gerarchia di tipo part-whole (parte-totalità). Questo pattern permette ai client di trattare oggetti singoli o loro raggruppamenti in modo uniforme;
  • Decorator: aggiunge dinamicamente responsabilità addizionali ad un oggetto. Questo pattern fornisce un meccanismo alternativo e flessibile all’ereditarietà per estendere le funzionalità base;
  • Facade: fornisce un’interfaccia unificata a un insieme di interfacce in un sottosistema. Questo pattern definisce un’interfaccia ad un livello più alto che rende il sottosistema più facile da usare, dato che ne maschera la complessità interna;
  • Flyweight: usa la condivisione per gestire in modo efficiente un numero considerevole di oggetti a granularità fine;
  • Proxy: fornisce un surrogato di un oggetto per controllare l’accesso ad esso.
I pattern comportamentali sono undici:
  • Chain of Responsability: evita di accoppiare il mittente di una richiesta con il suo destinatario dando la possibilità a più di un oggetto di gestire la richiesta. Collega tra loro gli oggetti ricevitori e fa passare la richiesta da un oggetto all’altro fino a destinazione;
  • Command: incapsula una richiesta in un oggetto, rendendo possibile parametrizzare i client con diverse tipologie di richieste, con richieste bufferizzate (queue), con richieste registrate (log) e con richieste annullabili (undo);
  • Interpreter: dato un linguaggio, definisce una rappresentazione della sua grammatica e del relativo interprete, che usa la rappresentazione per interpretare le frasi del linguaggio;
  • Iterator: fornisce un modo per accedere in modo sequenziale agli elementi di una collezione di oggetti senza esporre la sua rappresentazione sottostante;
  • Mediator: definisce un oggetto che incapsula le modalità di interazione di un insieme di oggetti. Questo pattern favorisce un basso accoppiamento, evitando che gli oggetti facciano riferimento l’uno con l’altro esplicitamente, e permette di variare le modalità di interazione in modo indipendente dagli oggetti stessi;
  • Memento: senza violare l’incapsulamento, recupera e rende esplicito lo stato interno di un oggetto in modo tale che l’oggetto stesso possa essere riportato allo stato originale in un secondo momento;
  • Observer: definisce una dipendenza uno-a-molti fra oggetti in modo tale che, se un oggetto cambia stato, tutti gli oggetti da esso dipendenti vengono notificati e aggiornati automaticamente;
  • State: permette ad un oggetto di modificare il suo comportamento quando il suo stato interno cambia;
  • Strategy: definisce una famiglia di algoritmi, li incapsula e li rende intercambiabili fra loro. Questo pattern permette di variare gli algoritmi in modo indipendente dal contesto di utilizzo;
  • Template Method: definisce lo scheletro di un algoritmo in un metodo di una classe base, delegando alcuni passi alle classi derivate. Questo pattern permette di ridefinire nelle classi derivate alcuni passi dell’algoritmo senza cambiare la struttura dell’algoritmo stesso;
  • Visitor: rappresenta un’operazione da svolgersi sugli elementi di una struttura ad oggetti. Questo pattern consente di definire nuove operazioni senza cambiare le classi degli elementi su cui opera. 

domenica 23 dicembre 2012

Design Pattern - Cosa Sono? - [0]


Progettare applicazioni basate sul paradigma object-oriented non è affatto banale, riuscire a renderle anche riusabili ed estendibili a piacimento è ancora più arduo. 

Ciò che occorre di volta in volta è saper individuare gli oggetti giusti, con un livello di dettaglio e granularità adeguato, ed essere in grado di definire una struttura gerarchica consistente, basata su un insieme di relazioni e collaborazioni coerente ed efficace.

Riuscire nell’intento di definire una struttura ad oggetti che sia corretta e al tempo stesso riusabile e flessibile è un’impresa ardua, soprattutto nel caso di applicazioni particolarmente complesse. In genere, durante la sua realizzazione, un’applicazione subisce innumerevoli modifiche dettate dalla variabilità delle specifiche progettuali, dalla scarsa conoscenza del dominio applicativo e dall’inesperienza. In più la mancanza di tempo, che nei progetti di sviluppo è un aspetto quasi congenito, porta sovente a scegliere soluzioni molto focalizzate sul modello reale attuale e poco orientate ad adattarsi ai cambiamenti futuri.
In uno scenario come quello descritto, diventa importante sia per chi progetta, sia per chi scrive il codice saper individuare delle soluzioni che siano riutilizzabili più volte nell’ambito di uno stesso progetto, piuttosto che in progetti diversi, senza ogni volta dover partire da zero per risolvere uno specifico problema.
Per minimizzare il lavoro da svolgere, gli sviluppatori meno esperti solitamente tendono a ricorrere a tecniche non object-oriented (non ultimo, il famigerato copia-e-incolla), con il risultato di duplicare parti di codice e di introdurre in modo più o meno voluto accoppiamento e dipendenze tra gli oggetti. E’ sicuramente preferibile ricercare soluzioni object-oriented che in passato si sono rivelate vincenti ed efficaci.
Queste soluzioni, che in prima analisi possiamo definire pattern, sono orientate a risolvere particolari problematiche di progettazione e tendono ad introdurre nell’ambito di una struttura ad oggetti quella flessibilità che è necessaria per rendere il codice riutilizzabile ed estendibile.
esistono diverse tipologie di pattern in funzione della loro area di applicazione, in generale essi possono essere raggruppati in macrocategorie specifiche (dette anche cluster), ciascuna delle quali contenente pattern orientati a risolvere problematiche similari. I cluster possono a loro volta essere suddivisi in sottocategorie a granularità più bassa.

Oltre che l’appartenenza ad un determinato cluster, per un pattern è possibile considerare come fattore distintivo anche il livello di astrazione che lo contraddistingue. Nell’ambito del cluster dei pattern relativi allo sviluppo di applicazioni software possiamo individuare tre categorie di pattern caratterizzate da un diverso livello di astrazione.

1. Pattern architetturali: descrivono lo schema organizzativo della struttura che caratterizza un sistema software. In genere questi pattern individuano le parti del sistema a cui sono associate responsabilità omogenee e le relazioni che esistono tra i diversi sottosistemi.
2. Pattern di disegno (design pattern): sono i pattern che si riferiscono alle problematiche legate al disegno object-oriented.
3. Pattern di implementazione (idiomi): sono pattern di basso livello specifici per una particolare tecnologia (per esempio, il .NET Framework). Essi descrivono le modalità implementative da utilizzare per risolvere problematiche di sviluppo sfruttando in modo mirato le caratteristiche peculiari di una particolare piattaforma.

Come detto, ciascuno di questi gruppi è caratterizzato da un grado di astrazione differente. I design pattern si collocano tra i pattern architetturali, troppo generici per essere orientati a risolvere problematiche di disegno, e i pattern idiomatici, molto legati alla tecnologia a cui si riferiscono e all’implementazione vera e propria. I design pattern descrivono soluzioni che lasciano sempre e comunque un certo grado di libertà nella loro adozione e implementazione, dal momento che non descrivono mai soluzioni che sono valide per una piattaforma specifica, ma al contrario hanno una validità più generale e trasversale rispetto alla tecnologia.

Uno degli aspetti più delicati nel disegno object-oriented (OOD) consiste nella scomposizione del sistema in oggetti. Si tratta di una attività complessa dal momento che entrano in gioco fattori non direttamente collegati alle specifiche funzionali quali l’accoppiamento tra oggetti, la loro dipendenza, la coesione funzionale, la granularità, la flessibilità, l’estendibilità e la riusabilità. Questi aspetti devono necessariamente influenzare il processo di scomposizione, talvolta anche in modi tra loro discordanti.

Esistono diversi approcci che permettono di scomporre in oggetti un sistema. È possibile partire dai casi d’uso, individuare in essi i sostantivi e i verbi e da questi ricavare le classi e i metodi corrispondenti al fine di ottenere la struttura ad oggetti desiderata. In alternativa, è possibile porre l’attenzione principalmente sulle responsabilità e sulle collaborazioni nell’ambito del sistema in fase di studio e, in funzione di esse, individuare gli oggetti necessari per gestirle opportunamente. O ancora, è possibile partire da un modello del mondo reale e tradurre gli elementi individuati in altrettanti oggetti.
Ognuno di questi approcci concorre a definire la struttura statica del sistema che, se da un lato rispecchia la realtà di oggi, dall’altro in genere non si presta direttamente ad evolvere nel tempo e ad adattarsi alla realtà di domani. L’esigenza di individuare una struttura flessibile ed estendibile, in grado di rispondere al meglio ai cambiamenti nel tempo, porta inevitabilmente a cercare soluzioni di disegno che facciano largo uso di astrazioni e oggetti non necessariamente collegati alla realtà in esame per non limitare in modo importante l’evoluzione del sistema.
I design pattern aiutano ad individuare queste astrazioni e gli oggetti in grado di rappresentarle. Essi concorrono a limitare le dipendenze, incrementando l’estendibilità e la riusabilità, e rendono di conseguenza il sistema più manutenibile nel tempo, in grado di rispondere ai cambiamenti in modo efficiente ed elegante.
I design pattern agevolano il riuso di soluzioni architetturali note, rendendo accessibili agli architetti e agli sviluppatori tecniche di disegno universalmente riconosciute come valide ed efficaci. In questo senso i design pattern aiutano i progettisti a operare scelte consapevoli tra le varie alternative possibili allo scopo di favorire la riusabilità.