Introduzione
Terminiamo idealmente con questa pubblicazione la
serie di tre articoli sul tema dello sviluppo di un
framework, iniziata nel mese di aprile 2001.
In questo intervento vedremo di spiegare alcuni elementi
di dettaglio: il motivo dell'utilizzo di involucri
(FileWrapper) per l'utilizzo di file, richiestomi
dopo i due precedenti articoli, e come creare gli
oggetti concreti senza che questi debbano essere conosciuti
nel codice del framework.
File Wrapper
I
file wrappers sono comparsi per la prima volta nella
definizione dell'interfaccia Arc.
public
interface Arc {
public Object getInput();
public Object getOutput();
public boolean equal(Arc other);
public int compareInputTo(Object otherInput);
public int compareOutputTo(Object otherOutput);
public void write(OutputFileWrapper outStream);
public Arc read(InputFileWrapper inputStream);
}
L'interfaccia
Arc viene utilizzata per la definizione della gerarchia
degli archi della macchina a stati finiti, come mostra
la seguente struttura [1]:
Figura 1
Vengono utilizzati durante le operazioni di input
e output con file. La loro funzionalità non
era stata mostrata perché non rilevante per
il design di base. Li si poteva considerare semplicemente
come rappresentanti di file.
Il motivo della loro presenza come wrapper diventa
però evidente se consideriamo la possibilità
di offrire più formati diversi dello stesso
contenuto.
Limitiamoci all'operazione read(). Partiamo da un
input di automa a stati finiti definito come file
di testo. Per questo file abbiamo una sintassi che
descrive il formato del testo e come questo formato
deve essere interpretato da Arc e State durante il
caricamento dei dati. Se ci fermassimo qui con l'analisi
dei requisiti, non avrebbe senso usare un wrapper
implementato da noi, basterebbe usare, invece di InputFileWrapper,
un parametro di tipo Reader o di tipo InputStream.
Basterebbe cioè lavorare direttamente con I
file.
Non vogliamo però fermarci qui.
Mantenendo in superficie la medesima sintassi, desideriamo
offrire la possibilità di leggere (e scrivere)
dati in modo binario, o, più utile, dati crittati
con uno dei molti algoritmi a disposizione.
Senza l'ausilio della classe wrapper, lo sviluppatore
dovrebbe a questo punto estendere la gerarchia di
Arc per poter prevedere la lettura del nuovo tipo
di file.
In altre parole lo sviluppatore dovrebbe produrre
un nuovo livello di gerarchia per un compito molto
specifico, che non dovrebbe essere considerato un
fattore decisivo nella distinzione di elementi di
tipo Arc. Lo svantaggio di questo approccio diventa
ancora più evidente se si considera che ci
sono altri elementi responsabili di input/output:
per tutti questi elementi bisognerebbe estendere la
gerarchia e prevedere le diverse forme di input e
output. Ci potrebbero inoltre essere elementi di tipo
final, come ad esempio State, la cui gerarchia non
può nemmeno essere estesa. Questo vuol dire
che lo sviluppatore del framework dovrebbe prevedere
ogni tipo di input/output, oppure, visto da un'altra
angolazione, significa che le applicazioni sarebbero
limitate ai tipi di input/output previsti dal framework.
Bridge
Pattern
Un
modo per evitare questa restrizione è quello
di separare le due gerarchie e unirle con un riferimento
a livello astratto. Questo è appunto lo scopo
di InputFileWrapper e OutputFileWrapper.
Entrambe
le interfacce presentano almeno una classe concreta.
Creazione di oggetti
In tutta l'implementazione del framework
abbiamo lavorato il più possibile con interfacce
e classi astratte. Lo scopo era quello di rendere
il codice del framework stabile e indipendente dalle
classi concrete utilizzate nelle applicazioni, cioè
nelle istanze del framework stesso.
Si tratta ora di risolvere il problema della creazione
degli oggetti concreti, senza ovviamente inserire
il nome delle classi nel codice del framework, vanificando
il lavoro di astrazione.
Il framework lavora con elementi astratti, ma in qualche
modo a questi elementi astratti in run time devono
essere associati oggetti concreti, che però
non possono essere previsti nella struttura del framework.
Factory
Method
Una
soluzione a questo problema viene proposta dal pattern
Factory Method ([4]), anche chiamato virtual constructor.
Il pattern parte dal presupposto che il framework
non conosce la classe concreta, ma conosce esattamente
il momento in cui un oggetto di questa classe deve
essere creato. Viene perciò chiamato un metodo
che restituisce l'oggetto giusto al momento giusto.
Nel nostro caso utilizziamo una classe che contiene
una lista di metodi factory. La classe è la
versione concreta di una classe astratta (Abstract
Factory) che ne definisce l'interfaccia.
public
abstract class FsmTool {
// Factory methods
public InputFileWrapper createInputFileWrapper(String
fileName){
return new TextInputFileWrapper(fileName);
}
public OutputFileWrapper createOutputFileWrapper(String
fileName){
return new TextOutputFileWrapper(fileName);
}
...
public abstract Traversal createTraversal();
public abstract Extractor createExtractor();
public abstract Arc createArc();
}
Da notare che non tutti i metodi factory devono necessariamente
essere astratti. La classe che funge da abstract factory
può infatti definire elementi di default (nel
nostro caso i due file wrappers). Solo gli ultimi
tre metodi sono astratti, perché, come mostrato
in [1] e [2], rappresentano l'essenza di questo framework.
Figura 4
Conclusione
Abbiamo così aggiunto una terza tappa al nostro
tragitto attraverso lo sviluppo di framework. Si tratta
di un meccanismo potente che ben utilizzato permette
lo sviluppo di applicazioni affidabili in tempi abbreviati.
L'individuazione di patterns all'interno dell'architettura
aumenta la flessibilità e l'estensibilità
del sistema, oltre che garantirne la documentazione.
È utile ricordare che l'utilizzo di patterns
non deve essere necessariamente previsto all'inizio
del design. Infatti i patterns sono spesso l'obiettivo
finale di iterazioni di refactorings [5].
Bibliografia
[1] Pedrazzini Sandro: Realizzazione di
un Framework: Primi Elementi di Design, Moka Byte,
http://www.mokabyte.com, Aprile 2001.
[2] Pedrazzini Sandro: Realizzazione di un Framework:
Adattabilità, Moka Byte, http://www.mokabyte.com,
Maggio 2001.
[3] Pedrazzini Sandro: The Jacaranda Framework, http://a.die.supsi.ch/~pedrazz/jacaranda
[4] Gamma E., Helm R.Johnson R., Vlissides J.: Design
Patterns, Elements of Reusable Object-Oriented Software,
Addison Wesley, 1995.
[5] Fowler Martin: Refactoring, Improving the Design
of Existing Code, Addison Wesley, 1999.
|