Il pattern MVC (Model–View–Controller) è uno dei modi più diffusi per organizzare il codice nelle applicazioni Java. Separare i ruoli di modello, vista e controller rende il software più manutenibile, testabile ed estendibile.
1. Che cos'è il pattern MVC
MVC separa l'applicazione in tre componenti principali:
- Model: rappresenta i dati e la logica di business (regole, validazioni, persistenza).
- View: si occupa di mostrare i dati all'utente (UI grafica, HTML, console, ecc.).
- Controller: riceve gli input dell'utente, aggiorna il Model e decide quale View mostrare.
L'idea chiave è che la View non conosce come i dati vengono gestiti a basso livello, e il Model non sa nulla di come verrà visualizzato: il Controller fa da ponte tra i due.
2. Vantaggi dell'uso di MVC in Java
- Separazione delle responsabilità: ogni classe ha un compito chiaro.
- Test facilitato: è più semplice testare Model e Controller separatamente.
- Riutilizzo delle View: puoi cambiare interfaccia (es. da Swing a JavaFX o web) senza riscrivere il Model.
- Manutenzione più semplice: il codice è meno accoppiato e più leggibile.
3. Strutturare un progetto Java in MVC
Una struttura tipica di un progetto Java desktop basato su MVC potrebbe essere:
src/
model/
ContoBancario.java
view/
ContoView.java
controller/
ContoController.java
App.java
In questo esempio svilupperemo una piccola applicazione di conto bancario con saldo, prelievo e deposito.
4. Implementare il Model
Il Model contiene lo stato dell'applicazione e la logica di business. Non ha riferimenti alla View.
package model;
public class ContoBancario {
private double saldo;
public ContoBancario(double saldoIniziale) {
this.saldo = saldoIniziale;
}
public double getSaldo() {
return saldo;
}
public void deposita(double importo) {
if (importo <= 0) {
throw new IllegalArgumentException("L'importo deve essere positivo");
}
saldo += importo;
}
public void preleva(double importo) {
if (importo <= 0) {
throw new IllegalArgumentException("L'importo deve essere positivo");
}
if (importo > saldo) {
throw new IllegalArgumentException("Saldo insufficiente");
}
saldo -= importo;
}
}
Punti chiave:
- Niente riferimenti a Swing, JavaFX o alla console.
- Espone metodi per le operazioni di business (deposita, preleva).
- Gestisce le validazioni (saldo sufficiente, importi positivi).
5. Implementare la View (esempio con Swing)
La View è responsabile solo dell'interfaccia utente. Espone metodi per:
- mostrare/leggere dati dalla UI;
- registrare listener di eventi che il Controller potrà usare.
package view;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JPanel;
import javax.swing.JOptionPane;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
public class ContoView extends JFrame {
private final JLabel saldoLabel = new JLabel("Saldo: 0.0");
private final JTextField importoField = new JTextField();
private final JButton depositaButton = new JButton("Deposita");
private final JButton prelevaButton = new JButton("Preleva");
public ContoView() {
super("Conto Bancario - MVC");
JPanel panel = new JPanel(new GridLayout(4, 2, 5, 5));
panel.add(new JLabel("Importo:"));
panel.add(importoField);
panel.add(depositaButton);
panel.add(prelevaButton);
panel.add(saldoLabel);
setContentPane(panel);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setLocationRelativeTo(null);
}
public String getImporto() {
return importoField.getText();
}
public void setSaldo(double saldo) {
saldoLabel.setText("Saldo: " + saldo);
}
public void mostraErrore(String messaggio) {
JOptionPane.showMessageDialog(this, messaggio, "Errore", JOptionPane.ERROR_MESSAGE);
}
public void pulisciCampoImporto() {
importoField.setText("");
}
public void aggiungiListenerDeposita(ActionListener listener) {
depositaButton.addActionListener(listener);
}
public void aggiungiListenerPreleva(ActionListener listener) {
prelevaButton.addActionListener(listener);
}
}
Punti chiave:
- La View non conosce il Model.
- Offre metodi per leggere l'input e aggiornare la UI.
- Espone metodi per registrare listener, ma non implementa la logica dei pulsanti.
6. Implementare il Controller
Il Controller collega Model e View: gestisce gli eventi dell'interfaccia, chiama i metodi del Model e aggiorna la View.
package controller;
import model.ContoBancario;
import view.ContoView;
public class ContoController {
private final ContoBancario model;
private final ContoView view;
public ContoController(ContoBancario model, ContoView view) {
this.model = model;
this.view = view;
this.view.setSaldo(model.getSaldo());
this.view.aggiungiListenerDeposita(e -> deposita());
this.view.aggiungiListenerPreleva(e -> preleva());
}
private void deposita() {
try {
double importo = Double.parseDouble(view.getImporto());
model.deposita(importo);
view.setSaldo(model.getSaldo());
view.pulisciCampoImporto();
} catch (NumberFormatException ex) {
view.mostraErrore("Inserisci un numero valido");
} catch (IllegalArgumentException ex) {
view.mostraErrore(ex.getMessage());
}
}
private void preleva() {
try {
double importo = Double.parseDouble(view.getImporto());
model.preleva(importo);
view.setSaldo(model.getSaldo());
view.pulisciCampoImporto();
} catch (NumberFormatException ex) {
view.mostraErrore("Inserisci un numero valido");
} catch (IllegalArgumentException ex) {
view.mostraErrore(ex.getMessage());
}
}
}
Punti chiave:
- Il Controller riceve riferimenti a Model e View nel costruttore.
- Registra i listener sulla View e implementa la logica nei metodi
depositaepreleva. - Traduce gli input utente in chiamate verso il Model e aggiorna la View in base al nuovo stato.
7. Classe di bootstrap dell'applicazione
Infine, creiamo la classe con il metodo main che inizializza Model, View e Controller.
import javax.swing.SwingUtilities;
import model.ContoBancario;
import view.ContoView;
import controller.ContoController;
public class App {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
ContoBancario model = new ContoBancario(1000.0);
ContoView view = new ContoView();
new ContoController(model, view);
view.setVisible(true);
});
}
}
In questo modo, l'applicazione parte creando le tre componenti principali e mostrando la finestra definita dalla View.
8. Suggerimenti pratici per usare MVC in Java
8.1 Non mettere logica di business nella View
Evita di scrivere codice come calcoli, validazioni complesse o accessi al database direttamente nei listener della UI. Quella logica appartiene al Model o al Controller.
8.2 Mantieni il Controller sottile
Se il Controller diventa troppo grande, valuta se spostare parte della logica in servizi separati o nel Model stesso, mantenendo il Controller come coordinatore.
8.3 Usa interfacce per disaccoppiare
Puoi definire interfacce per la View (es. ContoViewInterface) e far sì che la classe concreta
Swing le implementi. In questo modo puoi sostituire facilmente la View con una diversa implementazione
(per esempio una console o una web app).
public interface ContoViewInterface {
String getImporto();
void setSaldo(double saldo);
void mostraErrore(String messaggio);
void pulisciCampoImporto();
void aggiungiListenerDeposita(java.awt.event.ActionListener listener);
void aggiungiListenerPreleva(java.awt.event.ActionListener listener);
}
Il Controller dipenderà dall'interfaccia, non dalla classe concreta della View, riducendo l'accoppiamento.
9. MVC nel mondo Java web
Lo stesso concetto di MVC si applica anche alle applicazioni web Java:
- Model: classi di dominio, servizi, DAO, entità JPA.
- View: JSP, HTML/Thymeleaf, pagine JSF, template.
- Controller: Servlet, controller Spring MVC, backing beans.
Un controller Spring MVC tipico potrebbe essere:
@Controller
@RequestMapping("/conti")
public class ContoWebController {
private final ContoService contoService;
public ContoWebController(ContoService contoService) {
this.contoService = contoService;
}
@GetMapping("/{id}")
public String mostraConto(@PathVariable Long id, Model model) {
Conto conto = contoService.trovaPerId(id);
model.addAttribute("conto", conto);
return "dettaglioConto";
}
@PostMapping("/{id}/deposita")
public String deposita(@PathVariable Long id,
@RequestParam double importo) {
contoService.deposita(id, importo);
return "redirect:/conti/" + id;
}
}
Anche qui ritroviamo i tre ruoli: il Model è la logica di business, la View è la pagina HTML/TEMPLATE, e il Controller riceve le richieste HTTP, chiama il servizio e decide quale pagina restituire.
10. Conclusione
Usare il pattern MVC in Java ti permette di scrivere applicazioni più pulite, modulari e testabili. Separando Model, View e Controller:
- rendi più chiaro il flusso dei dati;
- semplifichi l'evoluzione dell'interfaccia utente;
- agevoli il riuso e il testing delle componenti di business.
Partendo dall'esempio di base presentato qui, puoi estendere l'architettura aggiungendo servizi, repository, gestione degli eventi più avanzata e integrazione con i principali framework Java (come Spring, JavaFX, Jakarta EE), mantenendo sempre al centro la separazione delle responsabilità.