MokaByte 52 - Maggio 2001
Foto dell'autore non disponibile
di
Andrea Gini
Sviluppo della GUI in applicazioni 
Java stand alone 
Parte III: modularita' 
In questo articolo parleremo di modularita' e di componibilita'. Un prodotto modulare e' composto da moduli indipendenti, spesso progettati separatamente, che vengono integrati in in fabbrica durante la fase di assemblaggio, come avviene per un'automobile; un  prodotto componibile e' un prodotto modulare che offre direttamente al cliente la possibilita' di comporre una soluzione su misura, al limite anche dopo l'acquisto, come nel caso degli impianti stereo o dei computers. In questo articolo illustreremo come introdurre modularita' e componibilita' nei programmi grafici

Un Programma e' un Insieme di Oggetti: la potenza del Polimorfismo
Nella vita di tutti i giorni capita spesso di trovarsi di fronte a problemi che possono essere risolti attraverso l'uso di una precisa combinazione di oggetti. L'insieme di oggetti di cui facciamo uso e le relazioni che creiamo tra di essi costituiscono la struttura, o il Framework, dell'attivita' che vogliamo svolgere. Per giocare a calcio ci servono i seguenti  oggetti: un campo, un pallone e una coppia di porte:


Figura 1 -  Un diagramma di Classe per il gioco del calcio......

Maradona e Pele', due tra i migliori calciatori della storia, agli inizi della loro carriera hanno messo in pratica lo stesso framework, con alcune.... trascurabili differenze implementative:


Figura 2 - ....altri oggetti, stesso gioco!

Da queste due maniere di vivere il gioco del calcio, possiamo ricavare un'astrazione che li descriva entrambi: il Framework del gioco del calcio


Figura 3 - Un Framework per il gioco del calcio e la sua 
implementazione professionale
 
 


Figura 4 - Stesso Framework, con la sua implementazione povera

Quando scriviamo un programma con un linguaggio ad oggetti, noi possiamo fare qualcosa di piu' che risolvere un particolare problema: possiamo infatti cercare di formulare una soluzione valida per un intera classe di problemi, uno per ogni possibile istanza. Proviamo a dare un'occhiata al diagramma delle classi del TextEditor:


Figura 5 - Diagramma delle classi di TextEditor

Questo diagramma puo' descrivere un gran numero di editor di testo, compreso, come vedremo, un semplice editor per programmi Java. Tutto quello che dobbiamo fare per passare dall'uno all'altro e' sostituire alcuni componenti con altri simili ma piu' specializzati: grazie a quella straordinaria caratteristica dei linguaggi ad oggetti che prende il nome di polimorfismo (la possibilita' di usare un oggetto al posto di un'altro equivalente), il nostro programma non dovrebbe nemmeno accorgersene.

Se vogliamo che i nostri programmi offrano la possibilita' di passare da un'implementazione all'altra, dobbiamo trovare il sistema per rendere piu' flessibile il processo di assemblaggio dei componenti, introducendo un meccanismo concettualmente simile ad un meccanismo ad incastro. Un sistema semplice e geniale viene descritto dal pattern Factory Method (GoF): nel prossimo paragrafo ne studieremo la struttura e l'uso.
 
 
 

Pattern Factory Method
Il Factory Method e' certamente il piu' semplice tra i pattern individuati dalla Gang of Four: in effetti la sua semplicita' concorre ad esaltarne il fascino e l'eleganza. Tutto quello che dobbiamo fare e' ritardare il processo di creazione di un oggetto, estraendo un metodo che svolga esclusivamente questo compito. Nel sorgente della settimana scorsa, che consigliamo di tenere a portata di mano, avevamo sviluppato un metodo setupComponents() che inizializza i principali componenti grafici:

  protected void setupComponents() {
    editor = new JTextArea();
    fileChooser = new JFileChooser();
    buildButtons();
    buildMenuItems();
  }

Per implementare il pattern Factory Method dobbiamo anzitutto modificare le prime due righe, quelle in cui vengono create la JTextArea e il JFileChooser, mettendo in pratica una particolare forma di estrazione di metodo:

  protected void setupComponents() {
    editor = createEditor();
    fileChooser = createFileChooser();
    buildButtons();
    buildMenuItems();
  } 

I metodi createEditor e  si limitano a istanziare l'oggetto e a restituirlo:

  protected JTextComponent createEditor() {
    return new JTextArea();
  }
  protected JFileChooser createFileChooser() {
    return new JFileChooser();
  }

E' importante notare che il nome del Factory Method deve evidenziare il ruolo dell'oggetto creato, piuttosto che il suo tipo: per questa ragione sono stati scelti come nomi createEditor() e createFileChooser() anziche' createJTextArea() e createJFileChooser(). In secondo luogo, il return type del metodo factory stabilisce il minimo requisito che un componente deve rispettare per poter essere accettato come componente adatto a svolgere quel ruolo: ad esempio il metodo createEditor() restituisce un JTextComponent e non una JTextArea. Il metodo chiamante, in questo caso setupComponents(), non deve fare nessuna assunzione sul particolare tipo di JTextComponent che gli viene restituito: in questa maniera, grazie al polimorfismo, il suo codice funzionera' indipendentemente dal fatto che il metodo Factory restituisca una JTextPane, una  JTextArea o una qualunque altra sottoclasse di JTextComponent.


Figura 6 - Il metodo createEditor() restituisce un JTextComponent, 
ma grazie al polimorfismo puo' andare bene qualunque 
sottoclasse di JTextComponent 

Come secondo intervento, modifichiamo i metodi buildMenuItems() e buildButtons() dopo aver aggiunto i metodi createMenuItem(String name,Icon icon) e createToolbarButton(Icon icon):

  protected void buildMenuItems() {
    OpenMenuItem = createMenuItem("Open",
                   new ImageIcon("Open24.gif"));
    SaveMenuItem = createMenuItem("Save",
                   new ImageIcon("Save24.gif"));
    CutMenuItem = createMenuItem("Cut",
                   new ImageIcon("Cut24.gif"));
    CopyMenuItem = createMenuItem("Copy",
                   new ImageIcon("Copy24.gif"));
    PasteMenuItem = createMenuItem("Paste",
                    new ImageIcon("Paste24.gif"));
  }

  protected JMenuItem createMenuItem(String name,Icon icon) {
    return new JMenuItem(name,icon);
  }
  protected void buildButtons() {
   OpenButton = createToolbarButton(new ImageIcon("Open24.gif"));
   SaveButton = createToolbarButton(new ImageIcon("Save24.gif"));
   CutButton = createToolbarButton(new ImageIcon("Cut24.gif"));
   CopyButton = createToolbarButton(new ImageIcon("Copy24.gif"));
   PasteButton = createToolbarButton(
                 new ImageIcon("Paste24.gif"));
  }

  protected AbstractButton createToolbarButton(Icon icon) {
    return new JButton(icon);
  }

Anche in questo caso abbiamo preferito fare in modo che il metodo createToolbarButton(Icon icon) restituisca un AbstractButton piuttosto che un JButton, lasciando aperto un numero maggiore di possibilita' di integrazione; nel caso del metodo createMenuItem(String name,Icon icon) abbiamo invece usato JMenuItem come valore di ritorno, dal momento che questo e' il minimo requisito che si chiede ad un componente che voglia essere inserito in una JMenuBar.

Per finire modifichiamo il metodo buildGui() ed aggiungiamo i metodi createMenuBar() e createToolBar():

  protected void buildGui() {
    JMenuBar menuBar = createMenuBar();
    JToolBar toolBar = createToolBar();

    getContentPane().setLayout(new BorderLayout());
    getContentPane().add(BorderLayout.NORTH,toolBar);
    getContentPane().add(BorderLayout.CENTER,
                   new JScrollPane(getEditor()));
    setJMenuBar(menuBar);
  }

  protected JMenuBar createMenuBar() {
    JMenu menu = new JMenu("Menu");
    menu.add(OpenMenuItem);
    menu.add(SaveMenuItem);
    menu.addSeparator();
    menu.add(CutMenuItem);
    menu.add(CopyMenuItem);
    menu.add(PasteMenuItem);
    JMenuBar menuBar = new JMenuBar();
    menuBar.add(menu);

    return menuBar;
  }
  protected JToolBar createToolBar() {
    JToolBar toolBar = new JToolBar();
    toolBar.add(OpenButton);
    toolBar.add(SaveButton);
    toolBar.addSeparator();
    toolBar.add(CutButton);
    toolBar.add(CopyButton);
    toolBar.add(PasteButton); 
    return toolBar;
  }

In questo caso particolare il Factory Method non si limita a creare il componente, ma ne imposta anche le principali proprieta'. Decidere cosa impostare dentro il Factory Method e cosa fuori e' un problema che va analizzato caso per caso: si cerchi sempre di restare coerenti con il nome del metodo, e di non cedere alla tentazione di mettere troppa carne al fuoco.
 
 
 

Giochiamo con la componibilita'
Il principale vantaggio del pattern Factory Method e' che permette di ritardare l'integrazione del sistema. In questo modo possiamo creare delle "variazioni sul tema" creando un'opportuna sottoclasse dell'oggetto principale che sovrascriva i Factory Method relativi agli oggetti che vogliamo rimpiazzare.

Nel primo articolo ci eravamo ripromessi di creare un editor con toolbar in radica e pulsanti in marmo: ora con poche righe possiamo creare esattamente un oggetto di questo tipo. Tutto quello che dobbiamo fare e' creare una sottoclasse di TextEditor che sovrascriva i metodi createToolbarButton(Icon icon) e createToolBar() facendo in modo che vengano generati degli oggetti con le opportune decorazioni:

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.text.*;
import java.awt.*;

public class KitchEditor extends TextEditor {

  protected AbstractButton createToolbarButton(Icon icon) {
    AbstractButton button = super.createToolbarButton(icon);
    Border buttonBorder = 
        BorderFactory.createMatteBorder(5,5,5,5,
            new ImageIcon("Texture_miscrock_018.jpg"));

    button.setBorder(buttonBorder);
    return button;
  }

  protected JToolBar createToolBar() {
    JToolBar tb = super.createToolBar();
    Border toolBarBorder = 
        BorderFactory.createMatteBorder(15,15,15,15,
            new ImageIcon("Texture_wood_012.jpg"));

    tb.setBorder(toolBarBorder);
    tb.setBackground(new Color(113,52,24));
    return tb;
  }

public static void main(String argv[]) {
  TextEditor k = new KitchEditor();
  k.setVisible(true);
}
}
 
 


Figura 7 - con poche righe possiamo stravolgere l'aspetto dell'editor





Specializziamo TextEditor
Cambiare l'ordine o l'aspetto dei componenti dell'interfaccia grafica grazie al Factory Method e' una possibilita' interessante e divertente, ma in fondo non particolarmente rivoluzionaria. Nel precedente esempio ci siamo limitati a creare un editor di testo piu' carino (opinione del tutto discutibile), ma pur sempre un TextEditor. Se cambiamo il colore alla nostra automobile, non possiamo dire di aver cambiato granche', ma se sostituiamo il motore con uno piu' potente probabilmente la modifica ci dara' ben altre soddisfazioni!

I metodi Factory, se usati correttamente, aggiungono la possibilita' di perfezionare un'intero Framework, specializzandone alcuni componenti. Se vogliamo realizzare un editor di sorgenti java a partire dal nostro Editor, sara' sufficiente definire una sottoclasse di TextEditor che generi un JTextComponent specializzato nel syntax higlighting di sorgenti Java (fornito insieme agli esempi), e che utilizzi un JFileChooser provvisto di filtri che abilitano unicamente il caricamento di file di tipo "*.java" o "*.jav":

public class JavaEditor extends TextEditor {

protected void setupFrame() {
  setTitle("Java Editor");
  setSize(300,300);
}

protected JTextComponent createEditor() {
  return new JavaEditorPane();
}

protected JFileChooser createFileChooser() {
  JFileChooser chooser = super.createFileChooser();

  ExampleFileFilter filter = new ExampleFileFilter();
  filter.addExtension("java");
  filter.addExtension("jav");
  filter.setDescription("Java Source");
  chooser.setAcceptAllFileFilterUsed(false);
  chooser.setFileFilter(filter);

  return chooser;
}

public static void main(String argv[]) {
  JavaEditor j = new JavaEditor();
  j.setVisible(true);
}
}


Figura 8 - un mini editor Java, realizzato a partire da
TextEditor grazie ai Factory Method

Questo esempio illustra in modo chiaro come la componibilita' renda possibile personalizzare un prodotto anche dopo la consegna. La componibilita' degli impianti stereo ha permesso, nei primi anni ottanta, di "estendere" i vecchi impianti, progettati anni prima che il CD fosse inventato, aggiungendo un lettore di Compact Disc:


Figura 9 - Componibilita' degli impianti Stereo 

Lo stesso principio permette a noi di estendere un banale TextEditor, dotandolo di capacita' non presenti nel progetto originale:


Figura 10 - Relazione tra TextEditor e JavaEditor. Si noti l'analogia con l'esempio del calcio





Conclusioni
In questo articolo abbiamo introdotto il concetto di sviluppo modulare di interfacce grafiche, ed abbiamo visto come sia possibile, grazie ai metodi Factory, allargare le possibilita' di utilizzo di un programma a finestre. L'uso dei Factory Method apre migliaia di possibilita': uno studio di alcuni casi esemplari (ogni componente Swing contiene almeno un esempio di Factory Method) permettera' di acquisire familiarita' con questa straordinaria tecnica di programmazione, e di trovarne nuove interessanti forme di utilizzo. Nel prossimo articolo affronteremo il tema della separazione tra sintassi e semantica di un programma grafico.

I componenti Swing
 Andrea Gini
 Mokabyte s.r.l.
 www.mokabyte.com

"UML Distilled" second edition
 Martin Fowler
 ADDISON-WESLEY

Refactoring : Improving the Design of Existing Code
 by Martin Fowler, Kent Beck (Contributor),
  John Brant (Contributor), William Opdyke, don Roberts 
 Addison-Wesley Object Technology Series
  http://www.refactoring.com/ 

Design Patterns: Elements of Reusable Object Oriented Software
 by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides, Grady Booch 
 Addison-Wesley Pub Co
 

L'esempio descritto in questo articolo può essere trovato qui

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