Introduzione ai Pattern

 

per collaborazioni, commenti, critiche, e altro contattateci alla e-mail: clubinfo@libero.it risponderemo al più presto!

di Luca Sabatucci

Indice

   Introduzione.
   Cosa è un pattern?
   Livelli di astrazione
   Componenti di un pattern
   Qualità di un pattern
   Alcuni esempi
   Conclusioni
   Anti pattern.
   Bibliografia.
   Alcuni siti interessanti:

 

Introduzione

Con la parola “software” non si intende il solo codice eseguibile localmente sulla macchina, bensì si tratta di un insieme di documenti, procedure, processi, regole, schemi, relativi allo sviluppo di una applicazione. Il codice eseguibile o i listati sorgenti sono solo una minima parte del blocco di documenti.

L’ingegneria del software insegna che la vita di un prodotto non dovrebbe finire mai, in quanto dopo le tre fasi per lo sviluppo:

1)      analisi

2)      progettazione

3)      codifica

esiste una quarta fase chiamata manutenzione, che prevede necessariamente di dover tornare indietro ad una delle tre fasi precedenti, per porre delle modifiche. Modificare un sistema software già esistente è molto importante, è viene fatto per tre motivi:

1)      eliminazione di difetti

2)      adattamento del software ai cambiamenti sociali (vedi euro)

3)      aggiunta di funzionalità

Il motivo per cui si preferisce intervenire su qualcosa di esistente, piuttosto che ricominciare l’attività da zero, è legato essenzialmente all’aspetto economico e temporale. Se infatti da un lato ricominciare da zero può portare a soluzioni più eleganti per certi problemi, dall’altro lato si incorre a costi troppo elevati per lavori già sostenuti e tempi molto lunghi senza per altro la sicurezza di ottenere un prodotto migliore.

Al livello più basso, fortunatamente, i linguaggi orientati agli oggetti vengono incontro a queste necessità offrendo importanti caratteristiche per la ricusabilità e la modificabilità del codice:

1)      information hiding

2)      incapsulamento

3)      ereditarietà

4)      polimorfismo

5)      overloading

Tuttavia questo miglioramento si ha solo nell’ultimo e per certi versi meno significativo (tutte le scelte progettuali sono già state fatte) aspetto dello sviluppo. E’ stato largamente dimostrato che intervenire su dei problemi nati in fase di analisi o di progettazione è molto più difficile (leggi costoso) che non in fase di codifica.

Per quanto riguarda queste due fasi, quindi, è molto difficile indicare come muoversi e quali siano le soluzioni migliori per affrontare determinati problemi, in quanto non essendo strutturate mediante linguaggi formali (a differenza della fase di codifica) non si può costruire così facilmente una cultura comune. Non è facile trovare le soluzioni migliori a problemi specifici, ci vuole molta esperienza, ed è un peccato non poter comunicare agli altri tale conoscenza, così da creare una vera e propria scienza ingegnieristica. Per far ciò risulta necessario stabilire un  vocabolario per esprimere tali concetti e un linguaggio per collegarli.

Per questo ci vengono incontro i pattern.

Cosa è un pattern?

Un pattern è un nucleo di idee, che contiene al suo interno l’essenza di una soluzione sicura e testata ad un problema ricorrente in un determinato contesto.

Non si tratta solo di un principio astratto o di una metodologia, ma è un insieme di elementi legati da delle relazioni ben precise, che porta ad una soluzione reale e concreta.

Sostanzialmente un pattern non indica solo cosa fare, ma specifica anche qual è la strada migliore per farlo: contiene l’aspetto pratico.

Livelli di astrazione

Tale concetto, può essere espresso a più livelli di astrazione:

Componenti di un pattern

L’esposizione di un pattern è effettuata mediante una descrizione in linguaggio naturale, ma segue una struttura logica ben precisa:

Qualità di un pattern

Trovandoci nel campo dell’ingegneria del software ha abbastanza senso parlare di qualità del pattern. Le caratteristiche di un pattern non sono molto diverse da quelle di un buon sistema software:

Alcuni esempi

Continuando a leggere l’articolo si troveranno alcuni esempi di pattern molto comuni. La presentazione di qualche esempio concreto mi sembra indispensabile affinché il concetto possa risultare più chiaro.

Singleton

Il primo esempio che riporto, è un pattern di basso livello, ovvero un idioma del C++.

Problema

Lo scopo di tale pattern è di risolvere il problema di creare una classe che possa essere instanziata una sola volta.

Componenti

Per risolvere questo problema basta definire una sola classe:

Soluzione

class Singleton {
public:
    static Singleton* Instance();  // gives back a real object!
    static proof(void);            // proof that the object was made
protected:
    Singleton();                   // constructor
private:
    static Singleton* _singleton;
};

 

Singleton* Singleton::_singleton = 0;
 
Singleton* Singleton::Instance() {
   if (_singleton == 0)
   {
      _singleton = new Singleton;
   }     // end if
   return _singleton;
}    // end Instance()

 

 

Il costruttore della classe è protetto, e quindi non può essere invocata nessuna istanza. Tuttavia l’unica istanza della classe è definita come puntatore (statico/privato) alla classe stessa. Quando viene chiesta una istanza mediante la funzione membro Istance(), se nessuna istanza è stata ancora definta, allora istanzia la memoria necessaria per l’oggetto, altrimenti ritorna il puntatore dell’oggetto già creato.

Vantaggi

Vediamo quali sono i vantaggi nell’usare questo pattern. Il controllo della singola istanza è assoluto, perché tale compito è svolto direttamente dalla classe stessa. La responsabilità del corretto funzionamento non è nelle mani del programmatore che andrà ad usarlo.

Il pattern, inoltre è semplice da estendere per permettere di creare degli oggetti Singleton più complessi. E’ molto facile inoltre estendere il pattern per far si che si possano istanziare un numero definito di oggetti (maggiore di uno).

Observer

Chi ha già programmato sotto Windows utilizzando le librerie MFC, oppure sotto dos con le Turbo Vision, si renderà conto che ha già usato questo pattern, pur non sapendo di che si trattava.

Problema

Questo pattern ritorna utile nel caso in cui si abbiano dei dati, dai quali dipendono più eventi. Ad esempio in un foglio di calcolo, il cambiamento di una cella può generare molte azioni, come il cambiamento di altre celle a causa di un legame, il cambiamento di un grafico, ecc… Gestire queste dipendenze può essere difficoltoso in quanto si possono facilmente generare dei cicli. Inoltre il sistema deve essere flessibile da permettere di aggiungere o eliminare delle dipendenze dinamicamente (se ad esempio si chiude il grafico, non è più necessario aggiornarlo, oppure si può aggiungerne un altro).

Una soluzione che non prevede l’applicazione del pattern è quella di lasciare agli oggetti il compito di andare a controllare periodicamente i dati, per verificare se ci sono cambiamenti. Risulta molto più efficiente fare in modo che sia l’oggetto osservato a notificare il proprio cambiamento agli oggetti osservatori.

Componenti

I partecipanti in questo pattern sono:

1)      il soggetto (ovvero i dati)

2)      gli osservatori

Soluzione

Tutti gli oggetti osservatori devono possedere una interfaccia comune, chiamata Observer, che definisce la funzione membro update() che il soggetto chiamerà quando ci sono delle modifiche.

Il soggetto, invece deve possedere tre funzioni fondamentali:

 

Factory

Un oggetto, come è noto può contenere altri oggetti come membri. In genere è meglio che l’oggetto base (chiamiamolo A) contenga un riferimento ad un altro oggetto (B), piuttosto che l’oggetto stesso.

Problema

Supponiamo che tale l’istanza dell’oggetto B  avvenga nel metodo costruttore di A. Un problema che nasce da tale soluzione è che qualora si abbia la necessità di derivare un oggetto C da A, e si vuole cambiare il modo in cui istanziare l’oggetto B, questo non è possibile. Vediamo un esempio in Java:

 

public class A {

     protected C riferim;

     public A() {

         riferim = new C();

     }

};

 

Soluzione

La soluzione consiste nel creare l’oggetto B esplicitamente, cioè lo si crea mediante un altro metodo. Nella classe derivata C è sufficiente ridefinire tale metodo per ottenere il comportamento voluto. Vediamo la soluzione:

 

public class A {

     protected B riferim;

     public A() {

         B = creaRiferim();

     }

     protected B creaRiferim () {

         return new B();

     }

}

 

Supponiamo adesso che R sia una classe derivata da B, per far usare R, piuttosto che B alla classe C basterà derivare e ridefinire creaRiferim():

 

protected B creaRiferim() {

     return new R();

}

 

Componenti

I componenti di questo pattern sono:

1)      prodotto, che definisce l’interfaccia dell’oggetto generato; nel nostro esempio si tratta di B.

2)      creatore, è il metodo che restituisce un oggetto di tipo prodotto; nel nostro caso creaRiferim();

Composite

Molto spesso nei programmi, gli oggetti vengono suddivisi in gerarchie. Tali gerarchie sono tali che un oggetto che vi appartiene può contenere al suo interno riferimenti ad altri oggetti della stessa gerarchia. Tali oggetti sono una sorte di contenitore per altri oggetti della gerarchia.

Problema

Il problema che si pone è quello di trovare una struttura per tali oggetti adatta per trattare allo stesso modo oggetti semplici e oggetti contenitori. Sarebbe un problema, infatti, trattare diversamente e due tipi di oggetti.

Soluzione

La soluzione sta nel creare una classe astratta (interfaccia) che rappresenta entrambi I tipi di oggetto.

Chi si interfaccia con un oggetto della gerarchia non sa (ne ha bisogno di sapere) di che oggetto si tratta, ma può usare i metodi comuni messi a disposizione. Deve essere l’oggetto composto che si deve preoccupare di propagare il comando ricevuto a tutti i suoi componenti.

Componenti

I componenti per tale pattern sono:

1)      Component: definisce la classe astratta che è l’interfaccia agli oggetti della gerarchia, sia oggetti semplici che composti.

2)      Leaf: rappresenta l’oggetto semplice, il quale non contiene altri oggetti della gerarchia.

3)      Composite: questo componente contiene al suo interno altri oggetti della gerarchia

Conclusioni

Anti pattern

Al concetto di pattern si contrappone l’anti-pattern. Si tratta sempre di un mezzo per diffondere conoscenza relativa ad un problema. Se un pattern rappresenta una “buona pratica”, un anti-pattern può essere visto come “una lezione appresa”. Gli anti-pattern si dividono in due tipi:

Non bisogna sottovalutare l’importanza di questo strumento perché è fondamentale perché la presenza di buoni pattern in un sistema software spesso non basta; bisogna anche poter dimostrare che per un sistema che non ha avuto successo, questo è dovuto al fatto che tali pattern non sono stati applicati.

Bibliografia

Il testo più significativo in campo di pattern è

E. Gamma, R. Helm, R. Johnson, J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, 1995, Addison_Wesley.

il quale ha dato inizio alla popolarità dei pattern nell’ambiente informatico.

Alcuni siti interessanti:

http://www.enteract.com/~bradapp/, sito curato da Brad Appleton in cui trovare un’ottima spiegazione sui pattern e un gran numero di siti connessi.

www.mokabyte.com, una rivista di informatica on-line in italiano che tratta tra i vari argomenti anche i pattern:

 

http://www.enteract.com/~bradapp/docs/patterns-intro.html , sito in cui trovare ulteriori notizie sui pattern.

Questo articolo è stato scaricato dal Club di informatica
Pagina curata da:
Luca Sabatucci