Lezione 14 · Canale 2 · martedì 27 ottobre 2026
Ascoltatori modulari e comandi nelle interfacce Swing
Progettazione del Software
Lezione 14 · Canale 2 · martedì 27 ottobre 2026
Progettazione del Software
Un'interfaccia ha molte sorgenti di eventi: ogni componente ne genera uno o più. Bisogna decidere quanti ascoltatori scrivere, dove metterli e a quali componenti collegarli. Il criterio è la modularità, e parte da una distinzione:
Un programma modulare le tiene in classi separate. Così la logica applicativa si riusa e la presentazione si cambia senza toccarla.
Le soluzioni già viste si leggono con questo criterio:
private dentro la finestra, e ne vede tutti i campi, anche privati. Non ci sono problemi di visibilità, ma le due logiche finiscono nella stessa classe.JFrame, implementa ActionListener e si registra con addActionListener(this). Una classe sola, ma le due logiche restano mescolate.Finestra e ascoltatore si scambiano i riferimenti:
this;public class EsempioTextFieldListener implements ActionListener {
private EsempioTextField frame;
public EsempioTextFieldListener(EsempioTextField f) {
frame = f;
}
public void actionPerformed(ActionEvent e) {
JTextField campo = frame.getTextField();
JOptionPane.showMessageDialog(null, campo.getText());
}
}
I campi della finestra restano privati. Per l'information hiding l'ascoltatore li raggiunge solo con i metodi della finestra, come getTextField(), che nascondono come la finestra è fatta dentro. Un ascoltatore esterno si può anche registrare su più finestre.
Finestra e ascoltatore esterno
Nei programmi più grandi le classi dell'interfaccia vanno in un pacchetto apposito, con un sottopacchetto per ogni finestra e i suoi ascoltatori. Le classi e i campi senza modificatore di accesso sono visibili alle sole classi dello stesso pacchetto. Gli ascoltatori dello stesso pacchetto leggono così i componenti senza metodi appositi: l'information hiding cala e le classi sono più accoppiate, quindi conviene solo per ascoltatori complessi o che lavorano con più finestre. import non cambia questi permessi: permette solo di scrivere il nome semplice di una classe.
Un ascoltatore per ogni componente e per ogni tipo di evento moltiplica le classi, e con molti componenti diventano difficili da gestire. Il tempo di avvio e la memoria occupata crescono con il numero di classi da caricare: serve un equilibrio fra modularità e prestazioni. Si raggruppano quindi in un solo ascoltatore le funzioni omogenee, per esempio tutte le voci di un menu.
Un ascoltatore che gestisce più componenti deve capire quale ha generato l'evento. Serve il comando:
c.setActionCommand(s) associa al componente c la stringa s;ActionEvent generato da c porta con sé quella stringa;e.getActionCommand().L'ascoltatore diventa un selettore: legge il comando e sceglie che cosa fare. Al contrario, due componenti che fanno la stessa cosa, come la voce di menu Salva e il pulsante Salva, ricevono lo stesso comando.
Una finestra ha tre voci, Up, Down e Random, e un solo ascoltatore. I comandi sono costanti dell'ascoltatore: public perché anche la finestra le usa, static final perché sono costanti, in maiuscolo per convenzione.
public class Listener implements ActionListener {
public static final String UPOPT = "up";
public static final String DOWNOPT = "down";
public static final String RANDOMOPT = "random";
public void actionPerformed(ActionEvent e) {
String com = e.getActionCommand();
if (com.equals(UPOPT)) {
upOpt();
} else if (com.equals(DOWNOPT)) {
downOpt();
} else if (com.equals(RANDOMOPT)) {
randomOpt();
}
}
private void upOpt() {
System.out.println("up");
}
private void downOpt() {
System.out.println("down");
}
private void randomOpt() {
System.out.println("random");
}
}
Se il comando non è fra quelli previsti, nessun ramo è scelto e non succede nulla. Il comando è una stringa, quindi si confronta con equals: == confronterebbe i riferimenti, non il contenuto.
La finestra registra lo stesso ascoltatore su ogni voce e dà a ogni voce il suo comando:
upOpt.addActionListener(ascoltatore);
upOpt.setActionCommand(Listener.UPOPT);
downOpt.addActionListener(ascoltatore);
downOpt.setActionCommand(Listener.DOWNOPT);
randomOpt.addActionListener(ascoltatore);
randomOpt.setActionCommand(Listener.RANDOMOPT);
Un ascoltatore per tre voci
Registrare l'ascoltatore non basta. Una voce senza setActionCommand usa come comando il proprio testo: Random manderebbe "Random", non "random", e il selettore non farebbe nulla.
actionPerformed legge il comando e chiama il metodo che lo gestisce, uno per comando; il lavoro non si scrive dentro actionPerformed. È come un centralino: collega l'evento dell'utente alla funzione che lo serve.
I metodi upOpt, downOpt e randomOpt stanno però ancora nell'ascoltatore, che quindi mescola un po' le due logiche. Il passo successivo li sposta in una classe di logica applicativa, anche in un altro pacchetto: i suoi metodi sono i servizi, e l'ascoltatore li chiama. Ne risulta un'architettura a livelli: uno strato gestisce l'interazione con l'utente (finestre e ascoltatori), un altro la logica applicativa.
Due logiche
Ascoltatore esterno
this dalla finestraComando
c.setActionCommand(s)e.getActionCommand()equalsSelettore