MokaByte 52 - Maggio 2001
Foto dell'autore non disponibile
di
Sandro
Pedrazzini
Realizzazione di un Framework
II parte: adattabilità
In questo articolo introduciamo il tema dello sviluppo di frameworks, utilizzando i patterns come punto di partenza per decisioni riguardanti design e implementazione.

Dopo le prime spiegazioni e i primi elementi di design definiti nello scorso articolo, questo mese scopriamo altri due importanti punti di adattabilità nell'architettura di un framework per macchine a stati finiti.

Introduzione
Nell'articolo precedente sulla realizzazione di frameworks abbiamo introdotto una prima astrazione utile per la realizzazione di un'architettura capace di mettere a disposizione un design riutilizzabile. Il design serve per l'implementazione di macchine a stati finiti.
La prima astrazione utilizzava il pattern interface/implementation per generalizzare gli arcs, elementi di transizione da uno stato all'altro della macchina a stati finiti.
 
 
 

Stati
Il tema centrale di questo articolo è l'adattabilità del framework. Ciò non significa che tutto sia adattabile. Anzi, per garantire l'utilizzo di un'architettura stabile è necessario che il framework presenti anche parti "fisse", in cui non esiste nessuna flessibilità. Queste rappresentano in un certo senso la spina dorsale del sistema.
L'implementazione degli stati è una di queste parti fisse. Per la sola adattabilità del sistema (utile al programmatore di applicazioni, concetto introdotto in [1]) non serve conoscere questi elementi. A noi interessa però l'intera architettura, perciò proponiamo almeno una breve spiegazione.

La classe State viene usata per rappresentare ogni singolo stato del sistema. Uno stato consiste in un riferimento ad un oggetto chiamato Flag (adattabile), contenente un certo numero di informazioni riguardanti lo stato, e ad un insieme di transizioni che permettono di passare dallo stato in questione ad un altro stato. La struttura e i collegamenti incrociati tra stati viene costruita direttamente a partire dal documento di input, contenente le informazioni specifiche per una singola macchina a stati finiti (FSM).
Ecco la rappresentazione grafica di questa mini architettura.
 



Figura 1

Da notare la presenza della classe WrapArc che fa da tramite tra State e Arc. Serve a gestire il collegamento allo State successivo, in modo che questa informazione non debba essere inclusa direttamente in Arc. Questo wrapper (pattern Proxy, [3]) serve ad evitare che il programmatore di applicazioni abbia a che fare con un riferimento di cui non dovrebbe preoccuparsi.
Ulteriori spiegazioni su State si possono trovare in [2].
 
 
 

Attraversamento
L' attraversamento di una FSM consiste nel passare da uno stato iniziale a tutta una serie di stati che possono eventualmente portare a uno stato finale.
L'idea che sta alla base dell'attraversamento "adattabile" consiste nel mettere a disposizione una serie di algoritmi che permettano l'attraversamento della struttura in modo differenziato, seguendo diverse strategie.
Vogliamo con questo realizzare nel framework una funzionalità di tipo plug-in, che ci permetta di sostituire a piacimento, anche in modo dinamico, in run-time, l'algoritmo di attraversamento.
Per questo tipo di realizzazione usiamo il pattern Strategy, già visto in [4], che con l'utilizzo del polimorfismo ci permette di racchiudere i diversi algoritmi sotto un'unica interfaccia. 
 



Figura 2

Il livello più alto è la Traversal. Lo sviluppatore di applicazioni ha a disposizione due classi concrete, oltre alla possibilità di implementarsene una sua particolare, ereditando da Traversal.
Ecco la classe astratta Traversal:

public abstract class Traversal { 
    protected Extractor extract; 

    public Traversal(Extractor e){ 
        extract = e; 
    } 

    public Extractor getExtractor(){ 
        return extract; 
    } 

    public abstract Extractor traverse
                            (State state, Sequence sequence, 
                             int traverse, Object byPassInfo); 
}
 

Da notare l'utilizzo di Extractor, spiegato nel prossimo capitolo.
 
 
 

Estrazione di informazioni
Durante l'attraversamento si passa da uno stato all'altro della FSM secondo le informazioni contenute negli elementi di tipo Arc. 
Da questi elementi dobbiamo anche estrarre informazioni di output (almeno per macchine di tipo transducer). La cosa più importante a questo punto è poter separare bene il processo di estrazione di informazione dall'algoritmo di attraversamento. Vogliamo mantenere questa separazione per poter riutilizzare stessi algoritmi di attraversamento con diversi tipi di dati e modi di interpretare le informazioni.
Non si tratta di creare un'indipendenza assoluta (l'estrazione dell'informazione dipende comunque dal tipo di attraversamento), ma di offrire l'opportunità di modificare il tipo di estrazione dei dati, senza "intaccare" il resto dell'applicazione, algoritmo di attraversamento compreso.
Abbiamo bisogno di un meccanismo di callback che abbia nel contempo la possibilità di mantenere uno stato. In altre parole abbiamo bisogno di un oggetto che sia in grado di accumulare le informazioni di output incontrate durante l'attraversamento. 
Per questo tipo di architettura utilizziamo il pattern Command ([3]), la cui astrazione di base è rappresentata dall'interfaccia Extractor, che ingloba il processo di estrazione di informazione. La struttura di stati e transizioni che verrà letta dall'oggetto rimarrà read-only.
Ereditare da Extractor significa implementare un nuovo modo di leggere informazioni durante l'attraversamento. La separazione della lettura di informazioni dall'algoritmo di attraversamento permette il riutilizzo indipendente di entrambi, in contesti diversi.
Ecco l'interfaccia Extractor:

public interface Extractor {
  public void visit(int keyHash, Arc arc, int traverse,
                    int level); 
    public void addInfo(Flag finalFlag, int level); 
    public boolean hasResults(); 
    public String iterateResults(Object filter,
                                 Object matchCriteria); 
}

Come si vede l'interfaccia definisce più di un metodo, per permettere una comoda interazione con il resto del framework. Quelli chiamati durante l'attraversamento sono però unicamente i metodi visit(), responsabile dell'accumulo di informazione ad ogni singola transizione, e addInfo(), responsabile, al termine dell'attraversamento, dell'accettazione della sequenza in input.
 



Figura 3



Consideriamo questa architettura un'astrazione "chiave" del framework (analogamente ad Arc, mostrato in [1]), perché, diversamente da Traversal, l'implementazione della classe concreta è molto specifica. Per questo il pattern Command è stato implementato con i tre livelli, seguendo il pattern interface/implementation.
In questo caso, visto che il livello astratto non deve contenere niente di particolare, possiamo utilizzarlo per inserire codice di debugging. 
Il codice di debugging nella classe astratta di un framework è molto utile per capire il flusso del programma, prima di implementare le classi concrete.
Così, ancor prima di implementare codice specifico, con una semplice dichiarazione di eredità è possibile utilizzare il framework e capire il funzionamento di una singola componente.

class TransExtractor extends AbstractExtractor {}

Con le informazioni di debugging ottenute in esecuzione lo sviluppatore di applicazioni è in grado di seguire l'andamento previsto dal framework. 
 

Conclusione
In questo articolo abbiamo compiuto un ulteriore passo nel processo di design del framework per l'implementazione di macchine a stati finiti.
Dopo aver visto in [1] l'architettura degli elementi di transizione, abbiamo mostrato una parte fissa del framework (State) e due importanti parti adattabili: attraversamento (Traversal) e estrazione di informazioni (Extractor).
 

Bibliografia
[1] Pedrazzini Sandro: Realizzazione di un Framework: Primi Elementi di Design, Moka Byte, http://www.mokabyte.com, Aprile 2001.
[2] Pedrazzini Sandro: The Jacaranda Framework, http://a.die.supsi.ch/~pedrazz/jacaranda
[3] Gamma E., Helm R.Johnson R., Vlissides J.: Design Patterns, Elements of Reusable Object-Oriented Software, Addison Wesley, 1995.
[4] Pedrazzini Sandro: Framework e Patterns: Documentare con Pattern, Moka Byte, http://www.mokabyte.com, Febbraio 2001.
 

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