Model View Controller Pattern (MVC)
di Claudio De Sio Cesari
-Introduzione
Il pattern in
questione è molto famoso ma è spesso utilizzato con
superficialità degli sviluppatori. Ciò è
probabilmente dovuto alla sua complessità, dal momento che
stiamo parlando di una vera e propria "composizione di
pattern". Venne introdotto nel mondo del software per la
costruzione di interfacce grafiche con Smalltalk-80, ma oggi deve
gran parte della sua fama a Java. L'MVC è stato infatti
utilizzato per la struttura di alcuni componenti Swing, e soprattutto
è coerente con l'architettura Java 2 Enterprise Edition
(J2EE).
Questo documento è liberamente ispirato, per
quanto riguarda la sua struttura, alla descrizione fornita proprio
dal catalogo dei pattern J2EE (Java Blueprints).
-Contesto
L'applicazione deve fornire una interfaccia grafica (GUI) costituita da più schermate, che mostrano vari dati all'utente. Inoltre le informazioni che devono essere visualizzate devono essere sempre quelle aggiornate [1].
-Problema
L'applicazione deve avere una natura modulare e basata sulle responsabilità, al fine di ottenere una vera e propria applicazione component - based. Questo è conveniente per poter più facilmente gestire la manutenzione dell'applicazione. Per esempio ai nostri giorni, con la massiccia diffusione delle applicazioni enterprise, non è possibile prevedere al momento dello sviluppo, in che modo e con quale tecnologia gli utenti interagiranno con il sistema (WML?, XML?, WI-FI?, HTML?). Appare quindi chiaro il bisogno di un'architettura che permetta la separazione netta tra i componenti software che gestiscono il modo di presentare i dati, e i componenti che gestiscono i dati stessi.
-Forze
E' possibile accedere alla gestione dei dati con diverse tipologie di GUI (magari sviluppate con tecnologie diverse)
I dati dell'applicazione possono essere aggiornati tramite diverse interazioni da parte dei client (messaggi SOAP, richieste HTTP...)
Il supporto di varie GUI ed interazioni non influisce sulle funzionalità di base dell'applicazione.
-Soluzione e struttura
L'applicazione
deve separare i componenti software che implementano il modello delle
funzionalità di business, dai componenti che implementano la
logica di presentazione e di controllo che utilizzano tali
funzionalità. Vengono quindi definiti tre tipologie di
componenti che soddisfano tali requisiti:
- il Model, che
implementa le funzionalità di business
- la View: che
implementa la logica di presentazione
- il Controller: che
implementa la logica di controllo
La seguente fig. 1, rappresenta
un diagramma di interazione, che evidenzia le responsabilità
dei tre componenti.

Fig. 1: "MVC: diagramma di interazione"
-Partecipanti e responsabilità
MODEL:
Analizzando la
fig 1, si evince che il core dell'applicazione viene implementato
dal Model, che incapsulando lo stato dell'applicazione definisce i
dati e le operazioni che possono essere eseguite su questi. Quindi
definisce le regole di business per l'interazione con i dati,
esponendo alla View ed al Controller rispettivamente le funzionalità
per l'accesso e l'aggiornamento. Per lo sviluppo del Model quindi è
vivamente consigliato utilizzare le tipiche tecniche di
progettazione object oriented al fine di ottenere un componente
software che astragga al meglio concetti importati dal mondo reale
[vedi OOA&D].
Il Model può inoltre avere la responsabilità di
notificare ai componenti della View eventuali aggiornamenti
verificatisi in seguito a richieste del Controller, al fine di
permettere alle View di presentare agli occhi degli utenti dati
sempre aggiornati.
VIEW:
La
logica di presentazione dei dati viene gestita solo e solamente
dalla View. Ciò implica che questa deve fondamentalmente
gestire la costruzione dell' interfaccia grafica (GUI) che
rappresenta il mezzo mediante il quale gli utenti interagiranno con
il sistema. Ogni GUI può essere costituita da schermate
diverse che presentano più modi di interagire con i dati
dell'applicazione. Per far sì che i dati presentati siano
sempre aggiornati è possibile adottare due strategie note
come "push model" e "pull model". Il push
model adotta il pattern Observer, registrando le View come
osservatori del Model. Le View possono quindi richiedere gli
aggiornamenti al Model in tempo reale grazie alla notifica di
quest'ultimo. Benché questa rappresenti la strategia ideale,
non è sempre applicabile. Per esempio nell'architettura J2EE
se le View che vengono implementate con JSP, restituiscono GUI
costituite solo da contenuti statici (HTML) e quindi non in grado di
eseguire operazioni sul Model. In tali casi è possibile
utilizzare il "pull Model" dove la View richiede gli
aggiornamenti quando "lo ritiene opportuno". Inoltre la
View delega al Controller l'esecuzione dei processi richiesti
dall'utente dopo averne catturato gli input e la scelta delle
eventuali schermate da presentare.
Controller:
Questo
componente ha la responsabilità di trasformare le interazioni
dell'utente della View in azioni eseguite dal Model. Ma il
Controller non rappresenta un semplice "ponte" tra View e
Model. Realizzando la mappatura tra input dell'utente e processi
eseguiti dal Model e selezionando la schermate della View richieste,
il Controller implementa la logica di controllo dell'applicazione.
-Collaborazioni
In fig. 2 viene evidenziata mediante un diagramma delle classi la vera natura del pattern MVC. In pratica, View e Model sono relazionati tramite il pattern Observer dove la View "osserva" il Model. Ma il pattern Observer caratterizza anche il legame tra View e Controller, ma in questo caso è la View ad essere "osservata" dal Controller. Ciò supporta la registrazione dinamica al runtime dei componenti. Inoltre il pattern Strategy potrebbe semplificare la possibilità di cambiare la mappatura tra gli algoritmi che regolano i processi del Controller e le interazioni dell'utente con la View [2].

Fig. 2: "MVC: diagramma delle classi"
Evidenziamo
ora solo due dei possibili scenari che potrebbero presentarsi
utilizzando un applicazione MVC-based: la richiesta dell'utente di
aggiornare dati e la richiesta dell'utente di selezionare una
schermata, rispettivamente illustrate tramite diagrammi di sequenze
nelle figure 3 e 4.

Fig. 3: "Scenario aggiornamento dati"
Dalla
figura 3 evidenziamo il complesso scambio di messaggi tra i tre
partecipanti al pattern, che benché possa sembrare inutile a
prima vista, garantisce pagine sempre aggiornate in tempo reale
all'utente.

Fig. 4: "Scenario selezione schermata"
Nella
figura 4 vengono enfatizzati i ruoli di View e Controller. La View
infatti non decide quale sarà la schermata richiesta,
delegando ciò alla decisione del Controller (che grazie al
pattern Strategy può anche essere anche cambiato dinamicamente
al runtime), piuttosto si occupa della costruzione e della
presentazione all'utente della schermata stessa.
-Codice d'esempio
Nell'esempio presentato di seguito, viene implementata una piccola applicazione che simula una chat, ma che funziona solo in locale, allo scopo di rappresentare un esempio semplice di utilizzo del pattern MVC. È un esempio didattico e quindi si raccomanda al lettore di non perdersi nei dettagli inutili, ma si consiglia piuttosto, di concentrarsi sulle caratteristiche del pattern. Nella figura 5, è rappresentato il diagramma delle classi della chat dal punto di vista dell'implementazione.

La classe Chat definisce il main che semplicemente realizza lo start up dell'applicazione, così come descritto dal diagramma di sequenza in figura 6.

La classe ChatView definisce due metodi setup1() e setup2(), per realizzare due schermate (molto simili in realtà, ma è solo un esempio). Essa definisce anche due classi interne anonime che fungono da listener. I listener una volta notificati, vanno a delegare all'oggetto (o agli oggetti) ChatController (registratisi in fase di start up come observer della View) mediante codice cicli di notifica come il seguente:
public
void itemStateChanged(ItemEvent e) {
ChatController
con;
for
(int i = 0; i < chatObserversVector.size(); i++) {
con
=
(ChatController)chatObserversVector.get(i);
con.processSelectionAction(e.getStateChange());
}
}
Di seguito è riportata l'intera classe Chat Controller:
public
class ChatController implements ChatObserver {
private
ChatModel model;
private
ChatView view;
private
int messageNumber = 0;
public
ChatController(ChatView view, ChatModel model) {
this.view
= view;
this.model
= model;
view.attach(this);
}
public
void update()
{
messageNumber++;
System.out.println(messageNumber);
}
public
void processSubmitAction(String msg)
{
model.addMessage(msg);
}
public
void processSelectionAction(int display)
{
view.showDisplay(display);
}
}
Si può notare come in questo esempio, il metodo update() non faccia altro che incrementare un contatore per contare messaggi, mentre la vera logica di controllo è implementata mediante i metodi (che potrebbero essere metodi strategy, ma in questo caso abbiamo semplificato) processSubmitAction() e processSelectionAction().
Per avere un quadro più completo riportiamo la classe ChatModel per intero (Per scaricare l'intero codice in file zip clicca qui):
import
java.util.*;
public class ChatModel implements ChatObservable
{
private
Vector messages;
private
Vector forumObserversVector = new java.util.Vector();
public
ChatModel() {
messages
= new Vector();
}
public
void addMessage(String msg)
{
messages.add(msg);
inform();
}
public
String getMessages() {
int
length = messages.size();
String
allMessages = "";
for
(int i = 0; i < length; ++i) {
allMessages
+= (String)messages.elementAt(i)+"\n";
}
return
allMessages;
}
public
void attach(ChatObserver
forumObserver){
forumObserversVector.addElement(forumObserver);
}
public
void detach(ChatObserver
forumObserver){
forumObserversVector.removeElement(forumObserver);
}
public
void inform(){
java.util.Enumeration
enumeration = forumObservers();
while
(enumeration.hasMoreElements())
{
((ChatObserver)enumeration.nextElement()).update();
}
}
public
Enumeration forumObservers(){
return
((java.util.Vector) forumObserversVector.clone()).elements();
}
}
-Conseguenze
Riuso dei componenti del Model. La separazione tra Model e View permette a diverse GUI di utilizzare lo stesso Model. Conseguentemente, i componenti del Model sono più semplici da implementare testare e manutenere, giacché tutti gli accessi passano tramite questi componenti.
Supporto più semplice per nuovi tipi di client, Infatti basterà creare View e Controller per interfacciarsi ad un Model già implementato.
Complessità notevole della progettazione. Questo pattern introduce molte classi extra, ed interazioni tutt'altro che scontate
-Conclusioni
Il pattern MVC, introduce una notevole complessità all'applicazione, ed è sicuramente non di facile comprensione. Tuttavia il suo utilizzo si rende praticamente necessario in tantissime applicazioni moderne, dove le GUI sono insostituibili. Non conviene avventurarsi alla scoperta di nuove interazioni tra logica di business e di presentazione, se una soluzione certa esiste già.
NOTE
Questo punto non è solitamente valido per architetture come J2EE dove le GUI non sono costituite da applicazioni, ma da pagine HTML.
Che si tratti di un pattern che ne contenga altri, risulta abbastanza evidente. Ma nella sua natura originaria l'MVC comprendeva anche l'implementazione di altri pattern come il Factory Method per specificare la classe Controller di default per una View, il Composite per costruire View, ed il Decorator per aggiungere altre proprietà (per es. lo scrolling).