Articoli Manifesto Tools Links Canali Libri Contatti ?
Design / Design Patterns

Strange Patterns 2

Abstract
Nuove idee per facilitare la stesura di un progetto software complesso! Nuovi Strange Pattern, presentati in questo articolo, per arricchire i Silver Bullet a vostra disposizione contro le difficoltà nel realizzare un prodotto riusabile.
Data di stesura: 01/04/2005
Data di pubblicazione: 23/04/2005
Ultima modifica: 04/04/2006
di Stefano Fago Discuti sul forum   Stampa

Courier e Binder Patterns

Il concetto di accoppiamento tra oggetti è una delle linee guida nella realizzazione di software: un basso accoppiamento è uno degli obiettivi a cui tendere nella ricerca del riuso dei diversi componenti realizzati. Dal mondo delle GUI (Graphic User Interface) emergono molte idee a riguardo ma, oltre al famoso paradigma/pattern MVC possiamo evidenziare elementi minori la cui importanza risulta evidente se si considerano UI particolarmente articolate: stiamo parlando del Courier e Binder Patterns.

Entrambi i design hanno come obiettivo quello di disaccoppiare gli oggetti collaboranti nel dato sistema, ma svolgono questo compito in modo leggermente differente: con questo presupposto possiamo intuire come questi pattern siano da considerarsi come variazioni sul tema dei mediatori(Mediator G.o.F. Pattern).

Il Courier Pattern viene presentato come oggetto che incapsula l'interazione tra due oggetti, promuovendo l'indipendenza dei differenti elementi coinvolti e la possibilità di alterare la logica di interazione stessa. Vengono introdotte due interfacce, PRODUCER e CONSUMER che verranno implementate dagli oggetti in gioco in base al ruolo rivestito; materiale di scambio tra i due elementi è l'oggetto MESSAGE.

  1. public interface Producer 
  2.   public Object produce(); 
  3.  
  4. public interface Consumer 
  5.   public void consume(Object message); 

L'oggetto Courier mantiene i riferimenti quanto al produttore che al consumatore e si occupa di passare un messaggio dal primo al secondo, dopo eventuali pre-processamenti, secondo la seguente forma:

  1. public class Courier 
  2.   ... 
  3.  
  4.   public void execute() 
  5.     Message m = producer.produce(); 
  6.  
  7.     preProcess(m); 
  8.  
  9.     consumer.consume(m); 
  10.  
  11.   ... 

L'attività del Courier è generalmente stimolata da un terzo oggetto detto INVOKER che provocherà, in conseguenza di un'azione dell'utente della GUI, l'invocazione della logica di coordinamento del mediatore:

  1. public interface Invoker 
  2.   public void action(); 

Nel mondo Swing questo design trova un immediata collocazione grazie alla presenza di meccanismi di listening che offrono un momento di ingresso alla realizzazione del pattern. Esempio immediato lo si può evidenziare nell'uso di oggetti di tipo Button. Un oggetto Button può essere costruito per rispondere alla sua pressione in base ad una data Action: una realizzazione dell'astrazione Action sarà l'oggetto Courier che vedrà come Invoker il Button associato all'azione stessa.

Supponiamo ora di avere una GUI in cui la scelta di un file, dopo un azione di browsing, permette la compilazione di un campo testuale che riporta il nome del file selezionato; l'azione di ricerca del file su un dato file chooser viene scatenata da un bottone dedicato.

La realizzazione di questa GUI è possibile utilizzando il Courier Pattern secondo quanto segue:

  1. public class Field extends JTextField implements Consumer 
  2.   public void consume(Object message) 
  3.     File msg = (File)message; 
  4.  
  5.     setText(msg.getAbsolutePath()); 
  6.  
  7. public class Chooser extends JFileChooser implements Producer 
  8.   public Object produce() 
  9.     JFrame owner = new JFrame(); 
  10.  
  11.     owner.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 
  12.     setDialogTitle("Choose a File"); 
  13.     setMultiSelectionEnabled(false); 
  14.     showOpenDialog(owner); 
  15.     owner = null; 
  16.  
  17.     return getSelectedFile(); 
  18.  
  19. public class Courier extends AbstractAction 
  20.   public Courier(Producer p ,Consumer c) 
  21.     ... 
  22.  
  23.   public void actionPerformed(ActionEvent actionEvent) 
  24.     execute(); 
  25.  
  26.   protected void execute() 
  27.     Producer prod = ... 
  28.     Consumer cons = ... 
  29.     Object message = null; 
  30.  
  31.     if (prod != null) { 
  32.       message = prod.produce(); 
  33.  
  34.     if (cons != null) { 
  35.       cons.consume(message); 

Il Binder Pattern è da considerarsi una visione idiomatica del Courier secondo il linguaggio Java. Il Binder è intermediario tra due oggetti, ma concepisce gli elementi in gioco come dei JavaBean: sfruttando questa specifica attua un automatico passaggio dei valori delle proprietà di interesse tra le due componenti. L'interfaccia del Binder è nella forma:

  1. public class Binder 
  2.   ... 
  3.  
  4.   public void setFirst(Object bean) 
  5.     ... 
  6.  
  7.   public void setSecond(Object bean) 
  8.     ... 
  9.  
  10.   public void setDirection(int direction) 
  11.     ... 
  12.  
  13.   public void bind(String[] firstProps, String[] secondProps, 
  14.     Converter[] converters) 
  15.     ... 
  16.  
  17.   public void execute() 
  18.     ... 
  19.  
  20.   ... 

Il Binder oltre a mantenere i riferimento agli oggetti da intermediare, è parametrico rispetto alle proprietà dei due elementi gestiti ed introduce una nuova interfaccia: il CONVERTER. Questo elemento si occuperà di rendere compatibile il messaggio nel momento dell'invocazione tra gli oggetti; si presenta della forma:

  1. public interface Converter 
  2.   public Object fromFirstToSecond(Object data); 
  3.   public Object fromSecondToFirst(Object data); 

L'uso del Binder, riprendendo il precedente esempio, sarà secondo la seguente sintassi:

  1. binder.setFirst(chooser); 
  2. binder.setSecond(field); 
  3. binder.setDirection(Binder.FIRST_TO_SECOND); 
  4.  
  5. binder.bind( 
  6.   new String[] {"selectedFile"}, 
  7.   new String[] {"text"}, 
  8.   new Converter[]{converter}); 

Immediatamente dopo la scelta del file si avrà l'invocazione:

  1. binder.execute(); 

Il Binder introduce un visione più dichiarativa e l'automatismo sulle invocazioni, che si presenta direzionabile. Svantaggi sono la perdita della semplicità e dell'immediatezza d'uso. Un altro vincolo è il riferirsi esplicitamente a delle proprietà e la mancanza di una logica di coordinamento. Questo problema è superabile concependo nel Binder un metodo che prenda in ingresso un BindTask secondo la forma:

  1. Binder 
  2.   ... 
  3.  
  4.   bind(BindTask task) 
  5.     ... 
  6.  
  7. public interface BindTask 
  8.   public void bindFromFirstToSecond(); 
  9.   public void bindFromSecondToFirst (); 

Un ulteriore variazione sullo stesso argomento è data dall'idea di SwitchBoard elemento condiviso tra Widget e nel quale sono registrati sotto forma di Command le callback relative agli eventi generati dai differenti componenti grafici. Al verificarsi di un data evento, il Widget stimola la SwitchBoard su uno specifico metodo; a sua volta, la board, esegue lo specifico Command registrato.

ISP e Object Adaptation

L'Interface Segregation Principle ci invita a creare delle interface che non forzino il Client di una data astrazione a dipendere da metodi che non utilizza: in ciò l'idea di avere interfacce coese e quindi evitare elementi FAT, carichi di responsabilità differenti. Il basso accoppiamento che viene stimolato trova la concezione più generica nelle Weak Interface(Interfaccia Debole): questi elementi sono caratterizzati da un protocollo semplicissimo, generalmente espresso da uno, massimo tre metodi; a volte la genericità viene rafforzata dall'uso di altre interfacce o classi astratte come argomenti e tipi di ritorno dei metodi d'interfaccia. Esponente illustre di questo design è il Command Pattern, che nella forma più semplice prevede un unico metodo: execute().

Partendo da queste idee e nell'osservazione che molte delle problematiche di collaborazione tra oggetti si esplicano in attività di conversione ed adattamento, nel mondo del linguaggio Python è stata introdotta una specifica di Object Adaptation basata su un semplice protocollo espresso nella funzione adapt(...). L'idea di fondo è relativamente semplice: un oggetto può supportare lo stesso tipo di protocollo richiesto o un protocollo compatibile, necessitare di adattamento o non supportare il protocollo. In Java, questa stessa idea vede come importante esempio la piattaforma Eclipse, grazie ad un elemento del suo runtime, l'interfaccia IAdaptable, espressa nella forma:

  1. public interface IAdaptable 
  2.   public Object getAdapter(Class target); 

In entrambi i linguaggi il momento di adattamento passa da una «semplice» operazione di casting alla creazione di un vero e proprio Adapter, istanza di un prototipo depositato in un registro che opera da Factory per tutti gli adattatori.

Vediamo un semplice esempio. Supponiamo di avere l'interfaccia Adaptable e gli elementi Table, Shape nonché i protocolli espressi dalle interfacce Writer e Reader. Vogliamo adattare quanto l'astrazione Shape che l'astrazione Table ai protocolli del Reader e del Writer.

  1. public interface Adaptable 
  2.   public Object adapt(Class targetType); 
  3.  
  4. public interface Reader { 
  5.  
  6.   public Object read();   
  7.  
  8. }                         
  9.  
  10. public interface Shape 
  11.   public float area(); 
  12.   public void draw(); 
  13.  
  14. public interface Writer 
  15.   public void write(Object o); 
  16.  
  17. public class Table implements Adaptable 
  18.   .... 
  19.   public Object adapt(Class targetType) 
  20.     return AdapterRegistry.getInstance().getAdapter(this, targetType); 
  21.   ... 

Il protocollo di adattamento viene a essere incapsulato nell'AdapterRegistry che fornirà l'opportuna Adapter in base alla seguente logica:

  1. l'oggetto da adattare è NULL, viene restituito NULL
  2. l'oggetto da adattare è compatibile con il protocollo del tipo target, viene restituito l'oggetto stesso
  3. l'oggetto appartiene ad una classe per cui è registrato un adapter, viene restituito l'adapter
  4. si cercano adapter registrati usando la super classe e le interfacce dell'oggetto
  5. viene restituito NULL

Un esempio d'uso può essere il seguente.

Allo startup del sistema vengono caricati nell'AdapterRegistry le diverse associazioni tra adapter e adattabili

  1. AdapterRegistry.getInstance().registerAdapter( 
  2.   Table.class, Reader.class, TableReaderAdapter.class); 
  3. AdapterRegistry.getInstance().registerAdapter( 
  4.   Table.class, Writer.class, TableWriterAdapter.class); 
  5. AdapterRegistry.getInstance().registerAdapter( 
  6.   Shape.class, Reader.class, ShapeReaderAdapter.class); 
  7. AdapterRegistry.getInstance().registerAdapter( 
  8.   Shape.class, Writer.class, ShapeWriterAdapter.class); 

A runtime vengono creati gli altri oggetti del sistema:

  1. Table t = new Table("Employee"); 
  2. Circle c = new Circle(); 

I collaboratori degli oggetti chiedono richiedono l'adattamento ad uno specifico protocollo:

  1. Writer w = (Writer) t.adapt(Writer.class); 
  2. Writer w2 = (Writer) c.adapt(Writer.class); 

Conclusioni

Le tematiche affrontate in questo articolo hanno fatto uso di materiale derivante dal mondo delle interfacce grafiche per evidenziare come a volte «guardando un po' più in là» è possibile trovare soluzioni di design interessanti, oltre le standardizzazione o le catalogazioni. Rimane sempre attivo l'obiettivo di stimolare la vostra curiosità e partecipazione e, perché no, la speranza di essere stato utile a qualcuno.

Informazioni sull'autore

Stefano Fago, classe 1973. Diplomato in ragioneria, ha conseguito il Diploma di Laurea in Informatica con un progetto legato alle interfacce grafiche soft-realtime in Java. Dopo esperienze in Alcatel ed Elea, ha svolto attività di consulenza come Software Developer e Trainer alla ObjectWay S.p.A. sede di Milano. Attualmente Software Designer presso la sezione Innovazione e Attività Progettuali di BPU Banca. Appassionato del linguaggio Java e di tutte le tecnolgie Object Oriented. Polistrumentista dilettante.

È possibile consultare l'elenco degli articoli scritti da Stefano Fago.

Altri articoli sul tema Design / Design Patterns.

Risorse

  1. Strange Patterns, prima parte.
    http://www.siforge.org/articles/2005/02/03-strangepatterns.html
  2. Sorgenti completi degli esempi.
    http://www.siforge.org/articles/2005/04/strangepatterns2/strangepatterns2.zip (14Kb)
  3. Courier Patterns di Robert Switzer
  4. SwitchBoard Pattern di S. Ramnath e B. Dathan - *
  5. PEP 246[Object Adaptation]: *
Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

Questo articolo o l'argomento ti ha interessato? Parliamone.