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). |