Come disaccoppiare completamente il modello da View / Controller in Java Swing


10

Esiste una raccolta di linee guida di progettazione comunemente concordate per separare le classi Model dalle classi View / Controller in un'app Java Swing? Non sono così preoccupato che View / Controller non sappia nulla del Modello come al contrario: mi piacerebbe progettare il mio Modello per non avere alcuna conoscenza di nulla in javax.swing. Idealmente dovrebbe avere una semplice API che gli consenta di essere guidato da qualcosa di primitivo come una CLI. Dovrebbe essere, in termini vaghi, un "motore".

Comunicare gli eventi della GUI al modello non è troppo difficile: gli Action Performer possono chiamare l'API del modello. Ma che dire di quando il modello apporta i propri cambiamenti di stato che devono essere riportati nella GUI? Ecco a cosa serve "ascoltare", ma anche "essere ascoltati" non è del tutto passivo; richiede che il modello sia a conoscenza dell'aggiunta di un listener.

Il problema particolare che mi ha fatto pensare riguarda una coda di file. Sul lato della GUI c'è un DefaultListModeldietro a JList, e ci sono alcune cose della GUI per scegliere i file dal file system e aggiungerli alla JList. Dal punto di vista del Modello, desidera estrarre i file dal fondo di questa "coda" (facendoli scomparire dalla JList) ed elaborarli in qualche modo. In effetti, il codice del modello è già scritto: attualmente mantiene un ArrayList<File>ed espone un add(File)metodo pubblico . Ma sono in perdita su come far funzionare il mio Modello con il View / Controller senza alcune pesanti modifiche specifiche di Swing al Modello.

Sono molto nuovo sia per la programmazione Java che per la GUI, finora ho sempre svolto la programmazione "batch" e "back-end", quindi il mio interesse a mantenere una rigida divisione tra modello e interfaccia utente, se è possibile e se può essere insegnato.



A proposito: in MVC il modello dovrebbe essere disaccoppiato dalla vista e dal controller per impostazione predefinita. Dovresti leggere di nuovo il tuo libro di testo, penso che non hai capito bene il concetto. E potresti implementare una raccolta personalizzata che avvisa quando cambia, proprio come l'interfaccia INotifyCollectionChanged di .NET.
Falcon,

@Falcon: grazie per i collegamenti. Non sono sicuro di aver compreso il tuo commento, tuttavia ... "il modello dovrebbe essere disaccoppiato dalla vista e dal controller per impostazione predefinita". Potresti riformulare o elaborare?
Cap

Risposte:


10

Non esistono linee guida di progettazione concordate (ovvero defacto ) per MVC. Non è del tutto difficile farlo da soli, ma richiede una certa pianificazione delle lezioni e un sacco di tempo e pazienza.

Il motivo per cui non esiste una soluzione definitiva è perché esistono diversi modi per eseguire MVC, tutti con i loro pro e contro. Quindi, sii intelligente e fai ciò che ti si addice meglio.

Per rispondere alla tua domanda, in realtà vuoi anche disaccoppiare il controller dalla vista (in modo da poter usare la stessa logica delle regole di business sia per un'app Swing che per l'app console). Nell'esempio di Swing si desidera disaccoppiare il controller da JWindowe qualsiasi altro widget in Swing. Il modo in cui ero solito fare (prima di utilizzare i framework effettivi) è creare un'interfaccia per la vista utilizzata dal controller:

public interface PersonView {
    void setPersons(Collection<Person> persons);
}

public class PersonController {

    private PersonView view;
    private PersonModel model;

    public PersonController(PersonView view, PersonModel model) {
        this.view = view;
        this.model = model;
    }
    // ... methods to affect the model etc. 
    // such as refreshing and sort:

    public void refresh() {
        this.view.setPersons(model.getAsList());
    }

    public void sortByName(boolean descending) {
       // do your sorting through the model.
       this.view.setPersons(model.getSortedByName());
    }

}

Per questa soluzione durante l'avvio è necessario registrare il controller nella vista.

public class PersonWindow extends JWindow implements PersonView {

    PersonController controller;
    Model model;

    // ... Constructor etc.

    public void initialize() {
        this.controller = new PersonController(this, this.model);

        // do all the other swing stuff

        this.controller.refresh();
    }

    public void setPersons(Collection<Person> persons) {
        // TODO: set the JList (in case that's you are using) 
        // to use the given parameter
    }

}

Potrebbe essere una buona idea creare un contenitore IoC per eseguire invece tutte le impostazioni.

Ad ogni modo, in questo modo è possibile implementare viste solo console, utilizzando gli stessi controller:

public class PersonConsole implements PersonView {

    PersonController controller;
    Model model;

    public static void main(String[] args) {
        new PersonConsole().run();
    }

    public void run() {
        this.model = createModel();
        this.controller = new PersonController(this, this.model);

        this.controller.refresh();
    }

    public void setPersons(Collection<Person> persons) {
        // just output the collection to the console

        StringBuffer output = new StringBuffer();
        for(Person p : persons) {
            output.append(String.format("%s%n", p.getName()));
        }

        System.out.println(output);
    }

    public void createModel() {
        // TODO: create this.model
    }

    // this could be expanded with simple console menu with keyboard
    // input and other console specific stuff

}    

La parte divertente è come gestire gli eventi. Ho implementato questo lasciando che la vista si registrasse al controller usando un'interfaccia, questo è fatto usando il modello Observer (se usi .NET, invece, useresti i gestori di eventi). Ecco un esempio di un semplice "osservatore di documenti", che segnala quando un documento è stato salvato o caricato.

public interface DocumentObserver {
    void onDocumentSave(DocModel saved);
    void onDocumentLoad(DocModel loaded);
}

// in your controller you implement register/unregister methods
private List<DocumentObserver> observers;

// register observer in to the controller
public void addObserver(DocumentObserver o) {
    this.observers.add(o);
}

// unregisters observer from the controller
public void removeObserver(DocumentObserver o) {
    this.observers.remove(o);
}

public saveDoc() {
    DocModel model = model.save();
    for (DocumentObserver o : observers) {
        o.onDocumentSave(model);
    }
}

public loadDoc(String path) {
    DocModel model = model.load(path);
    for (DocumentObserver o : observers) {
        o.onDocumentLoad(model);
    }        
}

In questo modo, la vista può aggiornarsi correttamente poiché si sta iscrivendo agli aggiornamenti del documento. Tutto quello che deve fare è implementare l' DocumentObserverinterfaccia:

public class DocumentWindow extends JWindow 
        implements DocView, DocumentObserver {

    //... all swing stuff

    public void onDocumentSave(DocModel saved) {
        // No-op
    }

    public void onDocumentLoad(DocModel loaded) {
        // do what you need with the loaded model to the
        // swing components, or let the controller do it on
        // the view interface
    }

    // ...

}

Spero che questi esempi motivanti ti diano alcune idee su come farlo da soli. Tuttavia, ti consiglio vivamente di prendere in considerazione l'utilizzo di framework in Java che fanno la maggior parte delle cose per te, altrimenti finirai per avere un sacco di codice boilerplate che impiega molto tempo per scrivere. Esistono un paio di piattaforme Rich Client (RCP) che è possibile utilizzare per implementare alcune delle funzionalità di base di cui probabilmente avrete bisogno, come la gestione dei documenti a livello di applicazione e molta gestione degli eventi di base.

Ci sono un paio che mi viene in mente: Eclipse e Netbeans RCP.

Devi ancora sviluppare controller e modelli per te stesso, ma è per questo che usi un ORM. L'esempio sarebbe Hibernate .

I container IoC sono fantastici, ma ci sono anche framework per questo. Come Spring (che gestisce anche la gestione dei dati, tra le altre cose).


Grazie per aver dedicato del tempo a scrivere una risposta così lunga. La maggior parte è un po 'sopra la mia testa a questo punto, ma sono sicuro che Wikipedia mi aiuterà. Io sto utilizzando sia Eclipse e Netbeans, e appoggiandosi a questi ultimi. Non sono sicuro di come distinguere il controller dalla vista lì, ma il tuo esempio pre-framework in alto aiuta.
Cap

0

La mia opinione su questo è che a volte dobbiamo scendere a compromessi

Come dici tu, sarebbe bello se potessimo propagare implicitamente la notifica di cambiamento senza che l'oggetto osservato avesse un'infrastruttura esplicita per questo. Per linguaggi imperativi comuni come Java, C #, C ++, la loro architettura di runtime è troppo leggera, almeno adesso. Con ciò intendo che al momento non fa parte delle specifiche del linguaggio.

Nel tuo caso particolare, non penso che sia esattamente una brutta cosa definire / utilizzare alcune interfacce generiche come INotifyPropertyChanged (come in c #), dato che comunque non è automaticamente accoppiato a una vista - dice solo che se cambio, Te lo dirò .

Ancora una volta, sono d'accordo che sarebbe fantastico se non dovessimo definire noi stessi la notifica di modifica, ma di nuovo se fosse implicito per tutte le classi potrebbe comportare un sovraccarico.


più ci penso, mi rendo conto che hai ragione: l'oggetto modello deve essere coinvolto in una certa misura nell'iniziare la notifica alla GUI che si è verificato un cambiamento; altrimenti la GUI dovrebbe eseguire il polling del modello per scoprire i cambiamenti - e questo è male.
Cap
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.