PDA

View Full Version : [Java] pattern Model-View-Controller e Swing


carosene
26-07-2007, 13:54
Sto provando a mettere in pratica il pattern Model-View-Controller in un'applicazione basata su Swing. Il dilemma è questo:
Come faccio in pratica a notificare alla View qualsiasi modifica al Model?
Più semplicemente:
Esiste una tecnica consolidata per aggiornare tutti i componenti di una gui che visualizzano un dato, quando quest'ultimo viene aggiornato?

mad_hhatter
26-07-2007, 15:06
Sto provando a mettere in pratica il pattern Model-View-Controller in un'applicazione basata su Swing. Il dilemma è questo:
Come faccio in pratica a notificare alla View qualsiasi modifica al Model?
Più semplicemente:
Esiste una tecnica consolidata per aggiornare tutti i componenti di una gui che visualizzano un dato, quando quest'ultimo viene aggiornato?

certo, applichi il pattern "observer"

PGI-Bis
26-07-2007, 15:25
MVC è una sigla parziale. Il pattern MVC è in verità MNVC, dove N sta per Notifiable. La N è quello che permette al modello di comunicare con le viste senza che le viste possiedano un riferimento al modello.

Riassumento in due righe pseudo-Javesche, lo schema è:

Model:
+addNotifiable(Notifiable n);

Notifiable:
+qualcosaNotify(...dati notificati)

View implements Notifiable, usa Controller

Controller usa Model

Quando il modello subisce una mutazione ad opera del controllo altro non fa che notificare la mutazione agli oggetti registrati come candidati alle notifiche (da cui il nome Notifiable). La vista o è un Notifiable o usa un Notifiable, in ogni caso è indirettamente collegata al modello.

carosene
26-07-2007, 16:40
Molte grazie per le risposte.

Ho scartato il modello "observer" perchè impone che l'oggetto osservato estenda Observable. Tutto questo mi limiterebbe nel creare una mia gerarchia di classi.

PGI-Bis è molto interessante quello che mi hai scritto. Potresti farmi un esempio pratico su come implementare il tutto?

mad_hhatter
26-07-2007, 17:06
Molte grazie per le risposte.

Ho scartato il modello "observer" perchè impone che l'oggetto osservato estenda Observable. Tutto questo mi limiterebbe nel creare una mia gerarchia di classi.

PGI-Bis è molto interessante quello che mi hai scritto. Potresti farmi un esempio pratico su come implementare il tutto?

in realtà non è necessario che estenda Observable (che a me non piace)... basta che ti fai una semplicissima interface Observable con i metodi addObserver, removeObserver e notifyObservers e un'interface Observer con un metodo update per ricevere le notifiche... così puoi crearti la tua gerarchia senza problemi

inoltre quanto detto da PGI-bis credo vada proprio in quella direzione: quello che lui chiama Notifiable è il mio Observer, mentre il Model è l'observable

PGI-Bis
26-07-2007, 19:18
Interessante per modo di dire. L'MVC sa un po' di stantìo :D.

Supponiamo che i dati contenuti nel modello siano... una stringa. Chi volesse ricevere una notifica sull'intervenuta mutazione dei dati del modello potrebbe semplicemente dire d'essere un:

public interface ModelNotifiable {

void dataChangeNotify(String value);
}

Ecco, l'MVC è bell'e che finito.

Il modello è una cosa che ha una stringa e un registro di oggetti "ModelNotifiable". Quando la stringa cambia, il modello piglia e invoca il metodo dataChangeNotify di ogni ModelNotifiable registrato. Per farlo basta una lista.

import java.util.*;

public class Model {
private LinkedList<ModelNotifiable> notifiables = new LinkedList<ModelNotifiable>();
private String data;

public void addNotifiable(ModelNotifiable n) {
notifiables.add(n);
}

public void setData(String value) {
this.data = value;
notifyDataChange();
}

private void notifyDataChange() {
for(int i = 0; i < notifiables.size(); i++) {
notifiables.get(i).dataChangeNotify(data);
}
}
}

Qui che si fa. Si fa:

Model model = new Model();
model.addNotifiable(la vista);

e fine della storia: a questo punto ogni volta che il modello rileva una mutazione del suo dato, spara un dataChangeNotify, la vista lo riceve e aggiorna la presentazione dei dati. Nulla di trascendentale.

Una vista degna di cotanto modello potrebbe essere:

import java.awt.*;
import javax.swing.*;

public class FakeView implements ModelNotifiable {
private JFrame window = new JFrame("FakeView Frame");
private JLabel dataLabel = new JLabel("", JLabel.CENTER);

public FakeView() {
window.add(new JLabel("Valore", JLabel.CENTER), BorderLayout.NORTH);
window.add(dataLabel, BorderLayout.CENTER);
window.setPreferredSize(new Dimension(300, 200));
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

public void show() {
EventQueue.invokeLater(new Runnable() { public void run() {
window.pack();
window.setLocationRelativeTo(null);
window.setVisible(true);
}});
}

public void dataChangeNotify(final String value) {
EventQueue.invokeLater(new Runnable() { public void run() {
dataLabel.setText(value);
}});
}
}

Una finestra con due etichette. Come noti FakeView è un ModelNotifiable. E' il fatto di essere un ModelNotifiable (o di possedere un ModelNotifiable, ma lo MVC "classico" vuole che la vista sia un notificabile) che consente alla vista di presentare dei dati senza avere cognizione del luogo da cui provengono.

Manca il controller. Originariamente (MVC nasce nel campo delle interfacce grafiche con il proposito di separare la presentazione dei dati dal dispositivo di input usato dall'utente) il controllo era un ponte tra l'utente ed il modello.

Ad esempio:

public class ConsoleController {
private Model model;

public ConsoleController(Model model) {
this.model = model;
}

public void start() {
java.io.Console console = System.console();
console.printf("Benvenuto nel 1967!%n");
console.printf("Inserisci una stringa e premi invio%n");
console.printf("La stringa quit chiude il programma%n");
String line;
while(!(line = console.readLine()).equals("quit")) {
model.setData(line);
}
console.printf("Fine dell'interazione%n");
}
}

Oggi è ancora un ponte tra l'utente ed il modello ma in un modo diverso. Per Controller si intende spesso il componente che si occupa di applicare la logica del programma all'input utente, dove l'input viene catturato dalla vista.

Bisogna stare attenti perchè le due interpretazioni sembrano simili ma non lo sono.

Un esempio di controller per Modello, che usa la console come periferica di input (ai tempi andati il controller avrebbe interagito direttamente con la tastiera), è:

public class ConsoleController {
public void link(Model model) {
java.io.Console console = System.console();
console.printf("Benvenuto nel 1967!%n");
console.printf("Inserisci una stringa e premi invio%n");
console.printf("La stringa quit chiude il programma%n");
String line;
while(!(line = console.readLine()).equals("quit")) {
model.setData(line);
}
console.printf("Fine dell'interazione%n");
}
}

Il punto d'entrata dell'applicazione MVC mette insieme i tre pezzi. Crea un modello, crea una vista, registra la vista come ricevitore di notifiche dal modello, crea il controllo. Quando il controllo è collegato al modello, l'utente può iniziare ad interagire con l'applicazione.

public class Main {

public static void main(String[] args) {
Model model = new Model();
FakeView view = new FakeView();
model.addNotifiable(view);
view.show();
ConsoleController controller = new ConsoleController();
controller.link(model);
}
}

carosene
26-07-2007, 23:51
PGI-Bis sei stato chiarissimo e gentilissimo.
Grazie di cuore…

carosene
06-08-2007, 18:31
Sto provando a mettere in pratica il tutto, per realizzare un programma che accede ad un database, visualizza i dati in diverse Jtable, in modo che possano essere modificati.
Per realizzare tutto questo ho creato tre package: Model, View e Controller.

Nel primo ho inserito tutte le classi che rappresentano un dato.
Nel secondo tutte le classi che si occupano di visualizzare i dati all'utente.
Nel terzo una classe che colleghi i primi due strati, e tutte le classi che incapsulano un'interazione con l'utente.

I dubbi sono questi:
Dove vanno inseriti i renderers delle celle?
Chiedo questo perché sono componenti grafici, ma comunque specifici di un modello. Di conseguenza mi riesce difficile immaginare un modo per separarli completamente.
Dove vanno inseriti gli editor di cella?
Al loro interno incapsulano un Component, ma al tempo stesso devono funzionare da controller, perché non tutti i dati inseriti dall'utente possono essere accettati.
Dove bisogna controllare l'input dell'utente?
Nel modello o nel controller?
Come gestire le modifiche dei dati?
In alcune circostanze, potrebbe essere necessario che per accettare una modifica di un determinato dato, ci debba essere una determinata condizione. Dove gestire il tutto: nel modello o nel controller?

Sto pensando a tutto questo perché immagino a come possa essere convertito un programma standalone in “altro”. Questo comporterebbe il rifacimento di tutto il controller e di tutte le viste. Giusto?

PGI-Bis
06-08-2007, 21:57
L'input utente è gestito dal controller. Intendo per input l'interazione dell'utente e non la validità delle informazioni immesse dall'utente.

La verifica dell'input è gestita dal modello o dal controllo. Dal modello se la validità dell'immissione dipende da un valore noto al modello. Altrimenti deve essere gestita dal controllo. Tale verifica assume le vesti di una convalida a posteriori solo nel caso di gestione da parte del modello. Nel caso in cui essa sia gestita dal controllo (in quanto non dipendente da uno stato del modello), la verifica è una semplice formattazione dell'input.

Due esempi astratti.

1. Verifica da parte del modello.

Il modello contiene delle chiavi a cui possono essere associati dei valori. Un valore può essere inserito solo se esiste una chiave a cui associare quel valore. L'utente inserisce coppie chiave (esistente) valore (nuovo). Chi controlla che la chiave esista prima di inserire il valore? Il modello. Lo fa il modello perchè la validità dell'input dipende da uno stato del modello (la presenza di una certa chiave).

2. Verifica da parte del controllo.

Il modello contiene numeri interi maggiori di 35. L'utente inserisce 30. Chi controlla che il numero sia maggiore di 35? Il controllo, perchè la validità dell'input non dipende da uno stato del modello. In questo caso il controllo opera come semplice "formattatore" dell'input. E' come se il modello richiedesse una sequenza di caratteri ed il controllo dovesse produrla a partire da una tastiera virtuale gestita col mouse. Il controllo si occupa di ricondurre una varietà di input ad un insieme predefinito di valori ciò che può essere fatto dal controllo in autonomia se e solo se i "limiti" di quell'insieme siano noti a priori. E' chiaro che questo può accadere solo se quei limiti non siano variabili. Nel caso in cui siano variabili, quella variabilità, in quanto informazione, sarà gestita dal modello.

Nous
07-08-2007, 09:00
La verifica dell'input è gestita dal modello o dal controllo. Dal modello se la validità dell'immissione dipende da un valore noto al modello. Altrimenti deve essere gestita dal controllo.

In altre parole, applica il pattern Expert e risolvi tutti i tuoi dubbi...chi deve fare un'operazione? L'esperto per il problema che l'operazione risolve ;)

pierosa
07-08-2007, 11:36
Ti propongo un esmpio semplice di un pattern molto simile al MVC, il pattern Model-View-Presenter(MVP).
In questo pattern Model e View sono gli stessi del MVC e il Controller è chiamto Presenter e si occupa anche di aggirnare la view.
Ci sono 2 varianti quella che ti presento è la Passive View.
Supponiamo di avere un form con 1 campi di testo dove puoi inserire un nome e cliccado su un pulsante esegui una ricerca in una rubrica telefonica, visualizzando il numero di telefono in una label.
Non uso java da troppo tempo per cui l'esempio sarà in pseudocodice simile a java.
Il Model è molto semplice

public class Model{
private String name;
private String number;

public void SetName(String name){
this.name = name;
}

public String getName(){
return this.name;
}
// set e get per number
}


LA view implementa una interfaccia che stabilisce un contratto tra la stessa view e il presenter. Questo ti permette di cambiare view a patto che questa implementi l'interfaccia richiesta

public interface ISearchView {
public void setNumber(String number);
public void displayError(String error);
}

public class SearchView implements ISearchView {
private SearchPresenter presenter;
private JTextField = new JTextField();
private JLabel numberLabel = new Jlabel();
private JLabel errorLabel = new Jlabel();
private JButton searchButton = new Jbutton();

public SearchView(){
// ogni view crea un presenter e si inietta nello stesso
presenter = = new SearchPresenter(this);
}

public void setNumber(String number){
numberLabel.setText(number);
}

public void displayError(String error){
errorLabel.setText(error);
}

public void buttonClick(){
// al click del bottone la view richiama il metodo search del presenter
// che si occupa di validare l'input e effettuare le operazioni richieste
presenter.search(nameTextField.getText());
}
}

come vedi la view conosce il presenter, nel costruttore crea un nuovo presenter e si inietta nello stesso.

Il presenter non conosce direttamente la view (intesa come implementazione specifica di una view) ma conosce l'interfaccia che essa implementa. Per cui può agire sulla stessa richiedendo delle oprazioni, il modo in cui le operazioni sarranno implementate dipende esclusivamente dalla view. Se devo mostrare un messaggio di errore il presenter esegue la richiesta, ma sarà la view a decidere come visualizzare l'errore (per esempio può visualizzare un testo in una label, oppure inserire un * vicino al campo non validato).

public class SearchPresenter{
private ISearchView view;
// supponiamo che ModelDao sia una classe che che effettua operazioni su database
ModelDao dao;
Model model;

public SearchPresenter(ISearchView view){
this.view = view;
}

public void search(String name){
// questo metodo vaiilda l'input
// esegue le operazioni di ricerca e aggiorna la view
// altrimenti notifica alla view di visualizzare un errore
if (controllo se name è valido){
model = dao.search(name);
view.setNumber(model.getNumber());
} else
view.displayError("Inserisci un nome");
}
}


e poi il main è semplice

public class Main {
public static void main(String[] args) {
SearchView view = new SearchView();
view.Show();
}
}


Ciao

carosene
07-08-2007, 17:00
Grazie per i vostri suggerimenti.
Ho tuttavia i seguenti dubbi:

Se nel modello si dovesse determinare che l'input è errato, si applica il pattern Observer per avvisare l'utente?



Nel pattern MVP, avere l'esigenza dello stesso Presenter in due componenti differenti della gui. È da considerare un errore di design dell'intero applicativo?



In entrambi i pattern MVP e MVC dove si gestiscono le meccaniche della gui? Ad esempio: solo se un elemento di un elenco è selezionato, bisogna abilitare un pulsante.

PGI-Bis
07-08-2007, 17:32
Le meccaniche della gui si gestiscono nel modello.

La vista fa vedere e stop.

Il pulsante è attivo solo se un elemento della lista è selezionato: significa che nel modello c'è un'informazione "elementoSelezionato" e un'informazione "stato del pulsante", che la mutazione del valore "elementoSelezionato" causa una mutazione del valore "stato del pulsante". Queste mutazioni sono comunicate alla vista tramite Notifiable e la vista aggiorna il suo aspetto in conseguenza della notifica ricevuta.

pierosa
07-08-2007, 17:57
Nel Passive view tutte le dinamiche della view sono gestite dal presenter (la vista è passiva, come dal nome)
all'evento della selezione della lista richiami il presenter notificandogli l'evento e di conseguenza il presenter richiederà l'aggiornamento della view attivando il bottone.

Nel pattern MVP, avere l'esigenza dello stesso Presenter in due componenti differenti della gui. È da considerare un errore di design dell'intero applicativo?

non ho ben capito cosa intendi

carosene
10-08-2007, 18:00
Scusatemi per il ritardo con il quale sto rispondendo.
Ho cercato di mettere in pratica il tutto, creando una semplice rubrica con il pattern MVP. Il programma non è di alcuna utilità, mi è servito solo per esercitarmi. Tecnicamente consente di aggiungere o rimuovere un contatto ad una collection, attraverso una JTable.
Mi piacerebbe molto avere qualsiasi vostro commento, perchè non sono un professionista o uno studente, ma un semplice hobbysta.



non ho ben capito cosa intendi

Volevo chiedere: Nel caso in cui un presenter dovesse gestire più di una finestra, non sarebbe più possibile crearlo nel costruttore della finestra. Come fare?