Man mano che i software diventano più complessi, la loro qualità rischia di calare. Per mantenerla alta si applicano i design pattern. Un Design Pattern è una soluzione generica che risolve un problema ricorrente; In pratica è un modo standard per scrivere determinati pezzi di codice. Quando si parla di interfacce grafiche si pensa al pattern MVC, usato molto nel web, oggi vediamo come si usa.
Il pattern MVC (Model View Control) prevede la suddivisione di un’interfaccia grafica in tre parti:
Separare il codice in tre parti distinte lo rende più pulito e comprensibile, ciò viene in aiuto quando dobbiamo sviluppare e debuggare applicazioni complesse.
Scriviamo un programma un po’ più complesso dei soliti utilizzando il pattern MVC. Dovremo realizzare una app rubrica, che per ogni contatto mostrerà nome, cognome e numero di telefono. Inoltre dovrà essere possibile aggiungere, modificare e rimuovere persone. Dovrà inoltre essere possibile importare ed esportare la rubrica su file.
Il risultato finale sarà questo qui, il codice completo lo trovi su github gists.
Facciamo un breve riepilogo dei requisiti software:
Cominciamo a pensare in che modo organizzare i componenti dell’app, partiamo facendo un wireframe della UI, cioè un disegno molto semplice di come vogliamo sia la nostra applicazione graficamente.
Ora che abbiamo un’ idea di come sarà la nostra app pensiamo a come definire le classi, partiamo dal view:
Dopo aver descritto la struttura della nostra app, realizziamo la gerarchia delle classi in UML:
Strutturiamo il codice in package, il risultato sarà il seguente:
📂
├── Controller/
│ └── Controller.java
├── Model/
│ ├── Contatto.java
│ ├── FileOps.java
│ └── Rubrica.java
├── View/
│ ├── ContattoDialog.java
│ └── Finestra.java
└── Main.java
Finestra mostra un JFrame con i componenti dell’app.
package View;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import java.awt.*;
public class Finestra {
private JPanel panel1;
private JButton aggiungiButton;
private JButton modificaButton;
private JButton rimuoviButton;
private JTable tabellaRubrica;
private JButton importaButton;
private JButton esportaButton;
private JFrame frame;
public Finestra() {
frame = new JFrame();
frame.setContentPane(this.panel1);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
ContattoDialog mostra un JDialog che permette di aggiungere o modificare un contatto.
package View;
import Controller.Controller;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ContattoDialog extends JDialog {
private JPanel contentPane;
private JButton buttonOK;
private JButton buttonCancel;
private JTextField nomeField;
private JTextField telefonoField;
private JTextField cognomeField;
private boolean buttonOKPressed = false;
public ContattoDialog(JFrame f, String title) {
setTitle(title);
initialize();
setLocationRelativeTo(f);
setVisible(true);
}
public ContattoDialog(JFrame f, String title, String nome, String cognome, String telefono) {
setTitle(title);
nomeField.setText(nome);
cognomeField.setText(cognome);
telefonoField.setText(telefono);
initialize();
setLocationRelativeTo(f);
setVisible(true);
}
Contatto contiene le informazioni necessarie per gestire un contatto, implementiamo Serializable per poter salvare su file la classe. Il metodo toArray() verrà usato in Rubrica.
package Model;
import java.io.Serializable;
public class Contatto implements Serializable {
private String nome;
private String cognome;
private String telefono;
public Contatto(String nome, String cognome, String telefono) {
this.nome = nome;
this.cognome = cognome;
this.telefono = telefono;
}
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public String getCognome() {
return cognome;
}
public void setCognome(String cognome) {
this.cognome = cognome;
}
public String getTelefono() {
return telefono;
}
public void setTelefono(String telefono) {
this.telefono = telefono;
}
public String[] toArray(){
return new String[] {nome, cognome, telefono};
}
}
Per poter rappresentare i dati sulla tabella, Rubrica estende AbstractTableModel, dovremo quindi implementare 4 funzioni:
package Model;
import javax.swing.table.AbstractTableModel;
import java.io.File;
import java.util.Vector;
public class Rubrica extends AbstractTableModel {
private Vector<Contatto> rubrica;
private String[] columnNames = {"Nome", "Cognome", "Telefono"};
public Rubrica(){
this.rubrica = new Vector<Contatto>();
}
public Rubrica(Vector<Contatto> rubrica){
this.rubrica = rubrica;
}
@Override
public int getRowCount() {
return rubrica.size();
}
@Override
public int getColumnCount() {
return columnNames.length;
}
@Override
public String getColumnName(int column) {
return columnNames[column];
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return rubrica.get(rowIndex).toArray()[columnIndex];
}
FileOps offrirà due metodi statici per leggere e scrivere su file un Vector di tipo Contatto.
package Model;
import java.io.*;
import java.util.Vector;
public class FileOps {
public static void writeAll(File f, Vector<Contatto> rubrica) throws IOException {
FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream oos = new ObjectOutputStream(fos);
for(Contatto p : rubrica){
oos.writeObject(p);
oos.reset();
}
}
public static Vector<Contatto> readAll(File f) throws ClassNotFoundException, IOException{
Vector<Contatto> rubrica = new Vector<Contatto>();
FileInputStream fis = new FileInputStream(f);
ObjectInputStream ois = new ObjectInputStream(fis);
try{
while(fis.available() != -1){
Contatto p = (Contatto) ois.readObject();
rubrica.add(p);
}
}
catch(EOFException e){}
return rubrica;
}
}
Controller implementa ActionListener, ascolta il click dei bottoni di Finestra e in base a quello che viene premuto mostra un JDialog o modifica Rubrica
package Controller;
import Model.*;
import View.*;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Controller implements ActionListener {
private Finestra finestra;
private Rubrica rubrica;
public Controller(Finestra f, Rubrica r){
this.finestra = f;
this.rubrica = r;
//Aggiungo l'action Listener ai vari bottoni dell' app
finestra.getAggiungiButton().addActionListener(this);
finestra.getModificaButton().addActionListener(this);
finestra.getRimuoviButton().addActionListener(this);
finestra.getImportaButton().addActionListener(this);
finestra.getEsportaButton().addActionListener(this);
finestra.getTabellaRubrica().setModel(rubrica);
}
@Override
public void actionPerformed(ActionEvent e) {
String errore = "";
// Filtro il componente che ha chiamato actionPerformed(), in base a questo eseguo azioni diverse
if (e.getSource() == finestra.getAggiungiButton()) errore = aggiungiPersona();
else if (e.getSource() == finestra.getModificaButton()) errore = modificaPersona();
else if (e.getSource() == finestra.getRimuoviButton()) errore = rimuoviPersona();