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

10

Esiste una raccolta di linee guida progettuali comunemente concordate per la separazione delle classi del modello dalle classi View / Controller in un'app Java Swing? Non sono così preoccupato che il View / Controller non sappia nulla del Modello come il contrario: mi piacerebbe progettare il mio Modello per non sapere nulla su javax.swing. Idealmente dovrebbe avere una semplice API che gli consenta di essere guidata da qualcosa di così semplice come una CLI. Dovrebbe essere, vagamente parlando, un "motore".

La comunicazione di eventi GUI al modello non è eccessivamente 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 riflessi sulla GUI? Questo è ciò che "ascolto" è per, ma anche "essere ascoltato" non è del tutto passivo; richiede che il modello sappia sull'aggiunta di un listener.

Il particolare problema che mi ha fatto pensare riguarda una coda di file. Sul lato GUI c'è un DefaultListModel dietro a JList , e ci sono alcune cose della GUI per scegliere i file dal file system e aggiungerli a JList. Dal lato del modello, vuole estrarre i file dalla parte inferiore di questa "coda" (facendoli scomparire dalla JList) e elaborarli in qualche modo. Infatti, il codice Modello è già stato scritto, al momento mantiene una ArrayList<File> e espone un metodo pubblico add(File) . Ma non riesco a capire come far funzionare il mio modello con View / Controller senza alcune modifiche pesanti e specifiche di Swing al modello.

Sono molto nuovo alla programmazione sia Java che GUI, avendo sempre fatto programmi di "batch" e "back-end" fino ad ora - da qui il mio interesse nel mantenere una rigida divisione tra il modello e l'interfaccia utente, se possibile e se può essere insegnato.

    
posta Chap 14.07.2011 - 08:03
fonte

2 risposte

10

Non ci sono linee guida di progettazione comunemente condivise (ad esempio defacto ) per MVC. Non è così difficile da fare da soli, ma richiede un po 'di pianificazione sulle tue lezioni e molto tempo e pazienza.

Il motivo per cui non esiste una soluzione definitiva è perché ci sono molti modi per fare 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 disaccoppiare anche il controller dalla vista (in modo da poter utilizzare la stessa logica delle regole di business sia per un'app Swing che per l'app della console). Nell'esempio Swing, si desidera disaccoppiare il controller da JWindow e qualsiasi widget in Swing. Il modo in cui ero solito fare (prima di usare i framework reali) è creare un'interfaccia per la vista che il controller usa:

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 fare tutto il setup per te.

In ogni caso, in questo modo puoi implementare le viste della sola console, usando 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 facendo sì che la vista si registrasse al controller usando un'interfaccia, questo è fatto usando il pattern Observer (se si sta usando .NET invece si useranno gestori di eventi). Ecco un esempio di un semplice "document observer", che segnala quando il 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 da quando si abbona agli aggiornamenti del documento. Tutto ciò che deve fare è implementare l'interfaccia DocumentObserver :

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 solo. Tuttavia ti consiglio caldamente di prendere in considerazione l'uso di framework in Java che fanno la maggior parte delle cose per te, altrimenti ti ritroverai con un sacco di codice boilerplate che richiede molto tempo per scrivere. Ci sono un paio di RCP (Rich Client Platform) che è possibile utilizzare per implementare alcune delle funzionalità di base più necessarie, come la gestione dei documenti a livello di applicazione e un sacco di gestione degli eventi di base.

Ci sono un paio che riesco a pensare dalla mia testa: Eclipse e Netbeans RCP's.

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

I contenitori IoC sono fantastici, ma esistono anche strutture per questo. Ad esempio Spring (che gestisce anche la gestione dei dati, tra le altre cose).

    
risposta data 14.07.2011 - 09:43
fonte
0

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

Come dici tu, sarebbe bello poter propagare implicitamente la notifica delle modifiche senza che l'oggetto osservato avesse un'infrastruttura esplicita per questo. Per i linguaggi imperativi comuni come Java, C #, C ++, la loro architettura runtime è troppo leggera, almeno fino ad ora. Con questo intendo che non fa parte delle specifiche del linguaggio in questo momento.

Nel tuo caso particolare, non penso che sia esattamente una brutta cosa definire / utilizzare qualche interfaccia generica come INotifyPropertyChanged (come in c #), poiché non è automaticamente accoppiata a una vista comunque - dice solo che se cambio, ti dirò .

Ancora una volta, sono d'accordo che sarebbe bello se non dovessimo definire noi stessi la notifica del cambiamento, ma poi di nuovo se fosse implicita per tutte le classi che potrebbe comportare un sovraccarico.

    
risposta data 14.07.2011 - 09:21
fonte

Leggi altre domande sui tag