MokaByte 49 - Febbraio 2001
Foto dell'autore non disponibile
di
Sandro Pedrazzini
Frameworks e Patterns
Documentare con Patterns
In questo articolo viene trattata la relazione tra framework e design patterns, mostrando come i patterns non solo servono durante lo sviluppo, ma anche quando si tratta di capire un'architettura.

Introduzione
Mai come negli ultimi due/tre anni il meccanismo di framework orientato a oggetti è stato utilizzato in modo così diffuso come succede attualmente. Non si tratta di un concetto nuovo: la programmazione ad oggetti, come sappiamo, viene fatta risalire anche a prima di Smalltalk. Sistemi operativi in cui una nuova applicazione non viene vista come estensione, ma come "istanza" di un framework prestabilito sono già stati sviluppati (per la verità non sempre con il successo sperato, basti pensare al progetto della Taligent). Ciò che però maggiormente ha portato al diffondersi di framework sono due elementi fondamentali: il linguaggio di programmazione Java con le sue classi incluse nel JDK, e la "percezione" dei design patterns, elementi di riutilizzo del software design. 
Il primo ha contribuito alla diffusione e al consolidamento della programmazione ad oggetti. Diffusione perché è ormai un linguaggio insegnato in moltissime università, consolidamento, perché è risultato essere un'attrazione per programmatori di C e C++, generando la necessaria massa critica.
Il secondo, il più importante, ha invece contribuito a dare una nuova visione del software e a rendere l'attività dello sviluppo software in un certo senso più "nobile". Ha cioè permesso di elevare il livello del discorso, pur mantenendo, anzi aumentando, l'importanza della programmazione, che all'interno di un progetto software non può in nessun caso essere considerata manovalanza (come ben specificato in [4]).
I design patterns da soli spiegano perché la programmazione ad oggetti è stata un passo in avanti. Dimostrano cos'è la genericità e come va sfruttato al meglio il polimorfismo.
I design patterns avvicinano alla programmazione e alla comprensione di framework perché in un certo senso ogni pattern è un mini framework. Ogni pattern rappresenta interazioni tra elementi, ogni pattern ha una sua descrizione precisa con esempi, vantaggi e svantaggi di suo utilizzo in casi particolari, peculiarità, ecc. Ogni pattern, infine, rappresenta un meccanismo generico che deve essere adattato (o meglio "istanziato", per restare alla terminologia object-oriented) esattamente come va fatto con un framework a oggetti.
Una combinazione di pattern, proprio grazie a questa loro intrinseca genericità, rappresenta un framework. Vale anche il contrario, cioè ogni framework può essere descritto attraverso un'interazione di pattern. 
E' proprio quest'ultima peculiarità che vorrei approfondire in questo mio primo intervento su frameworks e patterns, mostrando come la comprensione e l'utilizzo di elementi del JDK sarebbe molto più semplice se venissero menzionati in modo esplicito i patterns coinvolti.
 
 
 

Framework e Documentazione
Non è compito di questo articolo spiegare in dettaglio il significato di framework object-oriented. Mi limito a riportare alcune definizioni, reperibili in [1], [2], [3].
Per framework si intende più di un semplice toolkit. Si tratta di una serie di classi che collaborano tra di loro formando un design riutilizzabile per una specifica classe di software. Lo scopo di un framework è quello di definire la struttura di base di un'applicazione, classi e oggetti, e, soprattutto, la sequenza principale di un programma. Questi parametri iniziali permettono al programmatore di concentrarsi su parti specifiche della sua applicazione. Costruirà infatti l'applicazione utilizzando classi del framework o definendo sottoclassi di classi preesistenti, adattando, in altre parole, il framework nei suoi punti "flessibili", anche chiamati "hot spots".
Un framework può essere visto come un programma astratto. Solo la definizione di alcune classi concrete permette di generare un'applicazione. Il vantaggio consiste nel fatto che le decisioni più importanti riguardanti il design sono già state prese, permettendo uno sviluppo più veloce e una migliore manutenzione.
Anche un framework ha varie gradazioni. Non possiamo considerare un framework il JDK nel suo insieme, visto che molte sue parti sono semplici toolkit, cioè collezioni di classi di pura utilità, ma possiamo ritrovare nel JDK stesso più frameworks, ognuno dei quali è di supporto nell'implementazione di un compito ben preciso. Pensiamo ad esempio alle classi di AWT, all'estensione Swing e alle classi del Collection framework.
Ci sono vari modi per documentare un framework, o più in generale, un insieme di classi che collaborano. Questo è dovuto soprattutto al fatto che ci sono vari livelli di utenza di un framework. C'è chi integra in una propria applicazione programmi realizzati con un framework; c'è chi ha il compito di mantenere il framework per tutte le applicazioni che ne sono derivate; c'è chi utilizza il framework come modello per sviluppo di propri moduli software e c'è, infine, lo sviluppatore che utilizza il framework per implementare una sua applicazione specifica.
Ogni utente ha bisogno di un diverso livello di informazione, ma, tranne che per il primo tipo di utente, per gli altri è di fondamentale importanza conoscere, in modo più o meno dettagliato, l'architettura del sistema. Conoscere l'architettura significa capire il design del sistema ed essere in grado di gestire e sfruttare le parti flessibili ed adattabili del framework. Questo compito viene facilitato dalla descrizione del framework attraverso design patterns.
 
 
 

Gestione degli eventi AWT
Consideriamo come primo esempio la gestione degli eventi con il cosiddetto "delegation model" presente nelle classi AWT del JDK e riutilizzato in Swing.
Il modello si basa sulla separazione tra oggetto che genera l'evento e "ascoltatori" (listener) dell'evento stesso.
Quanto tempo si perde inizialmente per capirne il funzionamento? È vero che il tutto non viene per niente facilitato dagli esempi "didattici" forniti in molti tutorials, dove, pensando di semplificare la spiegazione, oggetto che genera l'evento e (unico) listener vengono implementati con la stessa classe. Sarebbe comunque più semplice specificare che la collaborazione tra oggetto che genera l'evento e listeners ricalca il modello descritto dal pattern Observer [1] (anche chiamato paradigma Model-View). Questo non solo spiegherebbe il design e il funzionamento, ma metterebbe in evidenza altri aspetti, come l'esatta collaborazione e dipendenza dei singoli elementi, il contesto in cui vengono utilizzati, vantaggi, svantaggi, ecc.
Insomma, con un'unica identificazione si potrebbe associare all'architettura un'intera serie di spiegazioni, valide anche per parti simili del JDK o di altri elementi framework riutilizzabili.
Lo schema che segue mostra l'architettura di base del pattern Observer. Gli elementi Subject e Observer sono astratti (classi astratte o, nel caso di Observer, "interface") e servono a definire le collaborazioni principali.


Figura 1

Se andiamo a leggere il catalogo dei patterns [1], troviamo che lo scopo del pattern Observer è quello di definire una dipendenza 1-m tra oggetti, in modo tale che quando uno modifica il suo stato, gli altri vengano informati.
Il suo utilizzo è utile quando la modifica di un oggetto rende necessaria la modifica di altri, ma non si sa quanti sono gli altri. La dipendenza tra oggetti è inoltre dinamica, può cioè cambiare in esecuzione.
Da notare che la dipendenza "subject" tra Observer e Subject, qui definita per mezzo di una variabile, può essere realizzata attraverso il passaggio di un parametro in update() che permetta di risalire a Subject, come effettivamente accade nel delegation model, mostrato nel prossimo schema.


Figura 2





Lo schema mostra la relazione tra classi per un tipo di listener, cioè il KeyListener. Il modello è comunque valido per tutti gli altri tipi di Listener, tra cui il MouseListener, MouseMotionListener, ActionListener, ChangeListener, ecc.
I due blocchi di commento specificano il nome del pattern coinvolto (maiuscolo) seguito dal nome dell'elemento del pattern a cui corrisponde la classe (Observer per KeyListener e Subject per Component).
 
 
 

Observer
L'interfaccia KeyListener rappresenta l'elemento Observer, che verrà notificato ad ogni evento di tipo KeyEvent registrato in Component.
I metodi da concretizzare sono in questo caso tre e corrispondono ai tre tipi di evento possibili attraverso l'utilizzo della tastiera.
Ognuno di questi metodi riceve come parametro un oggetto di tipo KeyEvent, che permette a Observer, tra le altre cose, di ottenere un riferimento sull'oggetto che ha mandato l'evento, attraverso la chiamata getSource() di KeyEvent. Questo realizza implicitamente il collegamento "subject" che nel pattern Observer è stato definito in modo esplicito.

void keyPressed(KeyEvent event){
   ...
   event.getSource();
   ...
}

Nel design non si considera il fatto che per comodità la classe concreta potrebbe implementare il Listener attraverso una classe intermedia chiamata "adapter" (in questo caso KeyAdapter), presente nel JDK.
 
 
 

Subject
L'elemento Subject in questo schema è rappresentato da Component. Da notare che Component contiene tutta una serie di metodi addXYZListener(), removeXYZListener() e processXYZEvent() che permettono la gestione di diversi meccanismi di Observer: per ogni evento esiste la serie di metodi per Observer.
Si può parlare anche in questo caso di pattern. Lo chiameremo add/remove/process pattern. Permette la realizzazione di più elementi Subjects del patterm Observer all'interno della stessa classe (Component).
Supponendo di avere in una nostra applicazione due classi concrete Listener (MyKeyListener e MyMouseListener), la loro registrazione all'interno dell'oggetto Subject avverrebbe in questo modo:

addKeyListener(new MyKeyListener());
addMouseListener(new MyMouseListener());
 

Realizzazione in Java
Vorrei da ultimo far notare che l'utilizzo del pattern Observer all'interno di un'applicazione Java viene facilitato dal JDK che mette a disposizione dello sviluppatore gli elementi Observable e Observer.
La prima è una classe che implementa alcuni metodi di utilità, tra cui addObserver(), deleteObserver() e notifyObservers() (che potremmo ricondurre al nostro add/remove/process pattern, se non ci fosse un'inconsistenza nella terminologia).
La seconda è la seguente interfaccia:

public interface Observer{
   public void update(Observable o, Object arg);
}

Si può notare come anche in questo caso il legame "subject" tra Observer e Subject venga realizzato attraverso il passaggio di Subject (in questo caso rappresentato da Observable) come parametro.
Altri elementi Observer esistono in JDK per realizzazioni più specifiche: interface ImageObserver e interface TileObserver.
 
 
 

Gestione del layout in AWT
Continuando la ricerca di design patterns all'interno del JDK, più precisamente in AWT o in Swing, ci imbattiamo nel pattern Strategy, utilizzato dal sistema di gestione del Layout.
In questo caso la conoscenza del pattern è meno determinante per l'utilizzo delle classi associate, visto che la funzionalità di LayoutManager viene sfruttata dal sistema e non direttamente dallo sviluppatore. Interessante è comunque conoscere l'architettura, perché attraverso questa è possibile capire proprio perché non serve conoscere i dettagli del funzionamento o degli algoritmi di layout usati. Inoltre, ovviamente, servirebbe nel caso in cui si desiderasse implementare un proprio LayoutManager.
Il pattern Stategy, come descritto nel catalogo, viene mostrato nello schema che segue.
 
 


Figura 3





Il suo scopo è quello di definire una famiglia di algoritmi interscambiabili. Il client (Context) utilizza uno di questi algoritmi attraverso un'interfaccia comune. L'utilizzo si rivela appropriato quando si desidera diversi algoritmi in tempi diversi con una configurazione dinamica.
I vantaggi consistono soprattutto nel fatto che la classe che chiama l'algoritmo non deve conoscere i dettagli dell'implementazione e non deve inserire controlli di condizione per sapere quale algoritmo chiamare: in caso di cambio di algoritmo la classe concreta di Strategy viene semplicemente sostituita.
Ecco come la gestione dei layout manager sfrutta il pattern Strategy:


Figura 4





In realtà la lista di implementazioni di LayoutManager è più lunga. Alcuni implementano LayoutManager2, che è un'estensione dell'interfaccia LayoutManager.
 
 
 

Conclusione
In questo articolo si è voluto mostrare che in ogni framework è possibile isolare una serie di patterns. Questo permette di comprendere meglio le collaborazioni e le interazioni tra classi e contribuisce alla comprensione dell'architettura migliorando notevolmente la documentazione del sistema.
Due patterns sono stati estratti dalla collaborazione tra classi nel JDK, più precisamente nel package java.awt. Altri verranno isolati e spiegati in un prossimo articolo.
 
 
 

Bibliografia
[1] Gamma E., Helm R.Johnson R., Vlissides J.: Design Patterns, Elements of Reusable Object-Oriented Software, Addison Wesley, 1995.
[2] Cotter S. Potel M.: Inside Taligent Technology, Addison Wesley, 1995.
[3] Govoni Derren: Java Application Frameworks, John Wiley & Sons, 1999.
[4] Gini Andrea: Refactoring, La qualità del software, MokaByte, gennaio 2001 (http://www.mokabyte.it/2001/01).


Sandro Pedrazzini ha studiato ingegneria informatica al politecnico di Zurigo e ha ottenuto il Ph.D. all'università di Basilea. Da anni si occupa di progettazione e sviluppo object-oriented. Attualmente collabora con l'università di Basilea in progetti di trasferimento tecnologico e tiene corsi di design e sviluppo software presso la SUPSI, Scuola Universitaria Professionale della Svizzera Italiana, a Lugano.
Vai alla Home Page di MokaByte
Vai alla prima pagina di questo mese


MokaByte®  è un marchio registrato da MokaByte s.r.l.
Java®, Jini®  e tutti i nomi derivati sono marchi registrati da Sun Microsystems; tutti i diritti riservati
E' vietata la riproduzione anche parziale
Per comunicazioni inviare una mail a
mokainfo@mokabyte.it