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 |