Das MVC-Muster und Swing


80

Eines der Designmuster, die ich im "echten Swing-Leben" am schwierigsten zu verstehen finde, ist das MVC-Muster. Ich habe einige der Beiträge auf dieser Site durchgesehen, in denen das Muster behandelt wird, aber ich habe immer noch nicht das Gefühl, ein klares Verständnis dafür zu haben, wie ich das Muster in meiner Java Swing-Anwendung nutzen kann.

Angenommen, ich habe einen JFrame, der eine Tabelle, einige Textfelder und einige Schaltflächen enthält. Ich würde wahrscheinlich ein TableModel verwenden, um die JTable mit einem zugrunde liegenden Datenmodell zu "überbrücken". Alle Funktionen, die für das Löschen von Feldern, das Überprüfen von Feldern, das Sperren von Feldern und Schaltflächenaktionen verantwortlich sind, werden normalerweise direkt im JFrame ausgeführt. Vermischt das nicht den Controller und die Ansicht des Musters?

Soweit ich sehen kann, gelingt es mir, das MVC-Muster beim Betrachten der JTable (und des Modells) "korrekt" zu implementieren, aber wenn ich den gesamten JFrame als Ganzes betrachte, wird es schlammig.

Ich würde wirklich gerne hören, wie andere diesbezüglich vorgehen. Wie gehen Sie vor, wenn Sie einem Benutzer mithilfe des MVC-Musters eine Tabelle, einige Felder und einige Schaltflächen anzeigen müssen?


2
Hier ist ein verwandtes Beispiel .
Trashgod

Für alle anderen, die zu dieser Party kommen - Swing ist KEINE reine MVC - leiht es sich schwer aus dem Konzept, sondern "kollabiert" die "Ansicht und den Controller" zusammen
MadProgrammer

Antworten:


106

Ein Buch, das ich Ihnen für MVC in Swing wärmstens empfehlen würde, wäre "Head First Design Patterns" von Freeman und Freeman. Sie haben eine sehr umfassende Erklärung von MVC.

Kurze Zusammenfassung

  1. Sie sind der Benutzer - Sie interagieren mit der Ansicht. Die Ansicht ist Ihr Fenster zum Modell. Wenn Sie etwas mit der Ansicht tun (z. B. auf die Schaltfläche Wiedergabe klicken), teilt die Ansicht dem Controller mit, was Sie getan haben. Es ist die Aufgabe des Controllers, damit umzugehen.

  2. Die Steuerung fordert das Modell auf, seinen Status zu ändern. Der Controller führt Ihre Aktionen aus und interpretiert sie. Wenn Sie auf eine Schaltfläche klicken, muss der Controller herausfinden, was dies bedeutet und wie das Modell basierend auf dieser Aktion bearbeitet werden soll.

  3. Der Controller kann auch die Ansicht auffordern, sich zu ändern. Wenn der Controller eine Aktion von der Ansicht empfängt, muss er die Ansicht möglicherweise anweisen, sich infolgedessen zu ändern. Beispielsweise könnte die Steuerung bestimmte Schaltflächen oder Menüelemente in der Benutzeroberfläche aktivieren oder deaktivieren.

  4. Das Modell benachrichtigt die Ansicht, wenn sich ihr Status geändert hat. Wenn sich im Modell etwas ändert, entweder aufgrund einer von Ihnen ergriffenen Aktion (z. B. Klicken auf eine Schaltfläche) oder einer anderen internen Änderung (z. B. Start des nächsten Songs in der Wiedergabeliste), benachrichtigt das Modell die Ansicht, dass sich sein Status geändert hat.

  5. Die Ansicht fragt das Modell nach dem Status. Die Ansicht erhält den angezeigten Status direkt vom Modell. Wenn das Modell beispielsweise der Ansicht mitteilt, dass ein neues Lied abgespielt wurde, fordert die Ansicht den Liednamen vom Modell an und zeigt ihn an. Die Ansicht fragt das Modell möglicherweise auch nach dem Status, wenn der Controller eine Änderung in der Ansicht anfordert.

Geben Sie hier die Bildbeschreibung ein Quelle (Wenn Sie sich fragen, was ein "cremiger Controller" ist, stellen Sie sich einen Oreo-Cookie vor, wobei der Controller das cremige Zentrum ist, die Ansicht der obere Keks und das Modell der untere Keks.)

Um, falls Sie interessiert sind , können Sie von einem ziemlich unterhaltsam Lied über das MVC - Muster Download hier !

Ein Problem bei der Swing-Programmierung besteht darin, den SwingWorker- und EventDispatch-Thread mit dem MVC-Muster zusammenzuführen. Abhängig von Ihrem Programm muss Ihre Ansicht oder Ihr Controller möglicherweise den SwingWorker erweitern und die doInBackground()Methode überschreiben , bei der ressourcenintensive Logik platziert wird. Dies kann leicht mit dem typischen MVC-Muster verschmolzen werden und ist typisch für Swing-Anwendungen.

EDIT # 1 :

Darüber hinaus ist es wichtig, MVC als eine Art Verbund verschiedener Muster zu betrachten. Beispielsweise könnte Ihr Modell mithilfe des Beobachtermusters implementiert werden (wobei die Ansicht als Beobachter für das Modell registriert sein muss), während Ihr Controller möglicherweise das Strategiemuster verwendet.

EDIT # 2 :

Ich möchte zusätzlich Ihre Frage speziell beantworten. Sie sollten Ihre Tabellenschaltflächen usw. in der Ansicht anzeigen, wodurch offensichtlich ein ActionListener implementiert wird. In Ihrer actionPerformed()Methode erkennen Sie das Ereignis und senden es an eine verwandte Methode in der Steuerung (denken Sie daran, dass die Ansicht einen Verweis auf die Steuerung enthält). Wenn Sie also auf eine Schaltfläche klicken, wird das Ereignis von der Ansicht erkannt und an die Methode des Controllers gesendet. Der Controller fordert die Ansicht möglicherweise direkt auf, die Schaltfläche oder etwas anderes zu deaktivieren. Als nächstes interagiert der Controller mit dem Modell und ändert es (das meistens Getter- und Setter-Methoden sowie einige andere Methoden zum Registrieren und Benachrichtigen von Beobachtern usw. enthält). Sobald das Modell geändert wird, wird ein Update für registrierte Beobachter aufgerufen (dies ist in Ihrem Fall die Ansicht). Daher wird die Ansicht jetzt selbst aktualisiert.


Ich habe das Buch tatsächlich gelesen, aber es fiel mir schwer, das Muster auf SWING anzuwenden. Ich habe auch gelesen, dass einige Stellen gelesen haben, dass ein JFrame sowohl als Ansicht als auch als Controller angesehen werden kann.
Sbrattla

... der JFrame ist eine Komponente und kein Blatt. In der Regel werden vom Controller vorgenommene Aktualisierungen an den JFrame gesendet, der sich um den Rest kümmert. Dies könnte daher die Illusion vermitteln, dass es sich um einen Controller handelt. In Wirklichkeit ist dies jedoch nicht der Fall, da das Modell nicht geändert wurde. nur die Aussicht. Wenn Ihr JFrame das Modell direkt geändert hat, machen Sie es falsch.
Dhruv Gairola

... wieder ist das Schlüsselwort hier "direkt". In Ihrem Fall können Sie Mausklicks auf die Tabelle abhören und die Logik an Methoden im Controller senden, die das Tabellenmodell ändern.
Dhruv Gairola

2
@DhruvGairola Die zweite Punktbeschreibung gilt für den dritten Punkt, der dritte und für Punkte haben die gleichen doppelten Beschreibungen. Können Sie sie bitte korrigieren?
Naruto Biju Mode

Das Lied ist ein Klassiker! =D
aaiezza

36

Ich mag die Idee nicht, dass die Ansicht diejenige ist, die vom Modell benachrichtigt wird, wenn sich seine Daten ändern. Ich würde diese Funktionalität an den Controller delegieren. In diesem Fall müssen Sie den Code der Ansicht nicht stören, wenn Sie die Anwendungslogik ändern. Die Aufgabe der Ansicht ist nur für die Anwendungskomponenten + Layout nicht mehr und nicht weniger. Das Layout in Swing ist bereits eine ausführliche Aufgabe. Warum sollte es die Anwendungslogik stören?

Meine Vorstellung von MVC (mit der ich gerade arbeite, soweit so gut) ist:

  1. Die Aussicht ist die dümmste der drei. Es weiß nichts über den Controller und das Modell. Es geht nur um die Prothetik und das Layout der Schaukelkomponenten.
  2. Das Modell ist auch dumm, aber nicht so dumm wie die Aussicht. Es führt die folgenden Funktionen aus.
    • ein. Wenn einer seiner Setter vom Controller aufgerufen wird, wird eine Benachrichtigung an seine Zuhörer / Beobachter ausgelöst (wie gesagt, ich würde diese Rolle dem Controller übertragen). Ich bevorzuge SwingPropertyChangeSupport, um dies zu erreichen, da es bereits für diesen Zweck optimiert ist.
    • b. Datenbankinteraktionsfunktionalität.
  3. Ein sehr intelligenter Controller. Kennt die Aussicht und das Modell sehr gut. Die Steuerung verfügt über zwei Funktionen:
    • ein. Es definiert die Aktion, die die Ansicht ausführt, wenn der Benutzer mit ihr interagiert.
    • b. Es hört auf das Modell. Wie gesagt, wenn der Setter des Modells aufgerufen wird, wird das Modell eine Benachrichtigung an den Controller auslösen. Es ist Aufgabe des Controllers, diese Benachrichtigung zu interpretieren. Möglicherweise muss die Änderung der Ansicht berücksichtigt werden.

Codebeispiel

Die Aussicht :

Wie gesagt, das Erstellen der Ansicht ist bereits ausführlich, erstellen Sie also einfach Ihre eigene Implementierung :)

interface View{
    JTextField getTxtFirstName();
    JTextField getTxtLastName();
    JTextField getTxtAddress();
}

Es ist ideal, die drei zu Testzwecken miteinander zu verbinden. Ich habe nur meine Implementierung von Model und Controller bereitgestellt.

Das Model :

public class MyImplementationOfModel implements Model{
    ...
    private SwingPropertyChangeSupport propChangeFirer;
    private String address;
    private String firstName;
    private String lastName;

    public MyImplementationOfModel() {
        propChangeFirer = new SwingPropertyChangeSupport(this);
    }
    public void addListener(PropertyChangeListener prop) {
        propChangeFirer.addPropertyChangeListener(prop);
    }
    public void setAddress(String address){
        String oldVal = this.address;
        this.address = address;

        //after executing this, the controller will be notified that the new address has been set. Its then the controller's
        //task to decide what to do when the address in the model has changed. Ideally, the controller will update the view about this
        propChangeFirer.firePropertyChange("address", oldVal, address);
    }
    ...
    //some other setters for other properties & code for database interaction
    ...
}

Der Controller :

public class MyImplementationOfController implements PropertyChangeListener, Controller{

    private View view;
    private Model model;

    public MyImplementationOfController(View view, Model model){
        this.view = view;
        this.model = model;

        //register the controller as the listener of the model
        this.model.addListener(this);

        setUpViewEvents();
    }

    //code for setting the actions to be performed when the user interacts to the view.
    private void setUpViewEvents(){
        view.getBtnClear().setAction(new AbstractAction("Clear") { 
            @Override
            public void actionPerformed(ActionEvent arg0) {
                model.setFirstName("");
                model.setLastName("");
                model.setAddress("");
            }
        });

        view.getBtnSave().setAction(new AbstractAction("Save") { 
            @Override
            public void actionPerformed(ActionEvent arg0) {
                ...
                //validate etc.
                ...
                model.setFirstName(view.getTxtFName().getText());
                model.setLastName(view.getTxtLName().getText());
                model.setAddress(view.getTxtAddress().getText());
                model.save();
            }
        });
    }

    public void propertyChange(PropertyChangeEvent evt){
        String propName = evt.getPropertyName();
        Object newVal = evt.getNewValue();

        if("address".equalsIgnoreCase(propName)){
            view.getTxtAddress().setText((String)newVal);
        }
        //else  if property (name) that fired the change event is first name property
        //else  if property (name) that fired the change event is last name property
    }
}

Der Main, in dem die MVC eingerichtet ist:

public class Main{
    public static void main(String[] args){
        View view = new YourImplementationOfView();
        Model model = new MyImplementationOfModel();

        ...
        //create jframe
        //frame.add(view.getUI());
        ...

        //make sure the view and model is fully initialized before letting the controller control them.
        Controller controller = new MyImplementationOfController(view, model);

        ...
        //frame.setVisible(true);
        ...
    }
}

4
Interessant, aber weniger effizient, wenn ein einzelnes Entitätsmodell in mehreren Ansichten angezeigt wird. Dann kann Ihr Entwurf dazu führen, dass ein "großer Controller" ein einzelnes Modell verarbeitet, aber alle zugehörigen Ansichten verwaltet. Und es wird noch schwieriger, wenn Sie versuchen, eine Reihe von "kleinen Modellen" wiederzuverwenden, dank einer Aggregation zu einem "großen Modell", da in einer Ansicht Informationen angezeigt werden, die in mehreren "kleinen Modell" -Entitäten versendet werden.
Yves Martin

1
@onepotato Ich habe gerade deine Codes ausprobiert. Wenn ich eine Taste drücke, kann ich die Codes in setUpViewEvents () auslösen. Wenn ich jedoch ein model.setSomething (123) mache, werden die Codes in propertyChange nicht ausgelöst. Ich habe sogar einen Druck direkt unter Object newVal = evt.getNewValue () abgelegt. und es wird nicht gedruckt.
AmuletxHeart

10
Dies ist NICHT das MVC- Architekturmuster, sondern das eng verwandte MVP -Muster (Model-View-Presenter). In einer typischen MVC ist es genau die Aufgabe des Modells, die Ansicht zu benachrichtigen, wenn sie sich geändert hat , genau das, was Sie "nicht mögen". Sehen Sie sich dieses Diagramm an, um zu sehen, wie Interaktionen in einer typischen MVC funktionieren.
MaxAxeHax

24

Das MVC-Muster ist ein Modell dafür, wie eine Benutzeroberfläche strukturiert werden kann. Daher definiert es die 3 Elemente Modell, Ansicht, Controller:

  • Modell Ein Modell ist eine Abstraktion von etwas, das dem Benutzer präsentiert wird. Im Swing haben Sie eine Unterscheidung zwischen GUI-Modellen und Datenmodellen. GUI-Modelle abstrahieren den Status einer UI- Komponente wie ButtonModel . Datenmodelle abstrahieren strukturierte Daten, die die Benutzeroberfläche dem Benutzer wie TableModel präsentiert .
  • Ansicht Die Ansicht ist eine UI-Komponente, die für die Darstellung der Daten für den Benutzer verantwortlich ist. Somit ist es für alle UI-abhängigen Probleme wie Layout, Zeichnung usw. verantwortlich, z . B. JTable .
  • Controller Ein Controller kapselt den Anwendungscode, der für eine Benutzerinteraktion ausgeführt wird (Mausbewegung, Mausklick, Tastendruck usw.). Controller benötigen möglicherweise Eingaben für ihre Ausführung und erzeugen Ausgaben. Sie lesen ihre Eingaben von Modellen und aktualisieren Modelle als Ergebnis der Ausführung. Sie können auch die Benutzeroberfläche umstrukturieren (z. B. Benutzeroberflächenkomponenten ersetzen oder eine vollständig neue Ansicht anzeigen). Sie dürfen jedoch nichts über die UI-Komponenten wissen, da Sie die Umstrukturierung in einer separaten Schnittstelle kapseln können, die der Controller nur aufruft. Im Swing wird ein Controller normalerweise von einem ActionListener oder einer Action implementiert .

Beispiel

  • Rot = Modell
  • Grün = Ansicht
  • Blau = Controller

Geben Sie hier die Bildbeschreibung ein

Wenn ButtonSie auf klicken, wird das aufgerufen ActionListener. Das ActionListenerhängt nur von anderen Modellen ab. Es werden einige Modelle als Eingabe und andere als Ergebnis oder Ausgabe verwendet. Es ist wie Methodenargumente und Rückgabewerte. Die Modelle benachrichtigen die Benutzeroberfläche, wenn sie aktualisiert werden. Die Controller-Logik muss also die UI-Komponente nicht kennen. Die Modellobjekte kennen die Benutzeroberfläche nicht. Die Benachrichtigung erfolgt durch ein Beobachtermuster. Somit wissen die Modellobjekte nur, dass es jemanden gibt, der benachrichtigt werden möchte, wenn sich das Modell ändert.

In Java Swing gibt es einige Komponenten, die auch ein Modell und einen Controller implementieren. ZB die javax.swing.Action . Es implementiert ein UI-Modell (Eigenschaften: Aktivierung, kleines Symbol, Name usw.) und ist ein Controller, da es ActionListener erweitert .

Eine ausführliche Erklärung, Beispielanwendung und Quellcode : https://www.link-intersystems.com/blog/2013/07/20/the-mvc-pattern-implemented-with-java-swing/ .

MVC-Grundlagen in weniger als 240 Zeilen:

public class Main {

    public static void main(String[] args) {
        JFrame mainFrame = new JFrame("MVC example");
        mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainFrame.setSize(640, 300);
        mainFrame.setLocationRelativeTo(null);

        PersonService personService = new PersonServiceMock();

        DefaultListModel searchResultListModel = new DefaultListModel();
        DefaultListSelectionModel searchResultSelectionModel = new DefaultListSelectionModel();
        searchResultSelectionModel
                .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        Document searchInput = new PlainDocument();

        PersonDetailsAction personDetailsAction = new PersonDetailsAction(
                searchResultSelectionModel, searchResultListModel);
        personDetailsAction.putValue(Action.NAME, "Person Details");

        Action searchPersonAction = new SearchPersonAction(searchInput,
                searchResultListModel, personService);
        searchPersonAction.putValue(Action.NAME, "Search");

        Container contentPane = mainFrame.getContentPane();

        JPanel searchInputPanel = new JPanel();
        searchInputPanel.setLayout(new BorderLayout());

        JTextField searchField = new JTextField(searchInput, null, 0);
        searchInputPanel.add(searchField, BorderLayout.CENTER);
        searchField.addActionListener(searchPersonAction);

        JButton searchButton = new JButton(searchPersonAction);
        searchInputPanel.add(searchButton, BorderLayout.EAST);

        JList searchResultList = new JList();
        searchResultList.setModel(searchResultListModel);
        searchResultList.setSelectionModel(searchResultSelectionModel);

        JPanel searchResultPanel = new JPanel();
        searchResultPanel.setLayout(new BorderLayout());
        JScrollPane scrollableSearchResult = new JScrollPane(searchResultList);
        searchResultPanel.add(scrollableSearchResult, BorderLayout.CENTER);

        JPanel selectionOptionsPanel = new JPanel();

        JButton showPersonDetailsButton = new JButton(personDetailsAction);
        selectionOptionsPanel.add(showPersonDetailsButton);

        contentPane.add(searchInputPanel, BorderLayout.NORTH);
        contentPane.add(searchResultPanel, BorderLayout.CENTER);
        contentPane.add(selectionOptionsPanel, BorderLayout.SOUTH);

        mainFrame.setVisible(true);
    }

}

class PersonDetailsAction extends AbstractAction {

    private static final long serialVersionUID = -8816163868526676625L;

    private ListSelectionModel personSelectionModel;
    private DefaultListModel personListModel;

    public PersonDetailsAction(ListSelectionModel personSelectionModel,
            DefaultListModel personListModel) {
        boolean unsupportedSelectionMode = personSelectionModel
                .getSelectionMode() != ListSelectionModel.SINGLE_SELECTION;
        if (unsupportedSelectionMode) {
            throw new IllegalArgumentException(
                    "PersonDetailAction can only handle single list selections. "
                            + "Please set the list selection mode to ListSelectionModel.SINGLE_SELECTION");
        }
        this.personSelectionModel = personSelectionModel;
        this.personListModel = personListModel;
        personSelectionModel
                .addListSelectionListener(new ListSelectionListener() {

                    public void valueChanged(ListSelectionEvent e) {
                        ListSelectionModel listSelectionModel = (ListSelectionModel) e
                                .getSource();
                        updateEnablement(listSelectionModel);
                    }
                });
        updateEnablement(personSelectionModel);
    }

    public void actionPerformed(ActionEvent e) {
        int selectionIndex = personSelectionModel.getMinSelectionIndex();
        PersonElementModel personElementModel = (PersonElementModel) personListModel
                .get(selectionIndex);

        Person person = personElementModel.getPerson();
        String personDetials = createPersonDetails(person);

        JOptionPane.showMessageDialog(null, personDetials);
    }

    private String createPersonDetails(Person person) {
        return person.getId() + ": " + person.getFirstName() + " "
                + person.getLastName();
    }

    private void updateEnablement(ListSelectionModel listSelectionModel) {
        boolean emptySelection = listSelectionModel.isSelectionEmpty();
        setEnabled(!emptySelection);
    }

}

class SearchPersonAction extends AbstractAction {

    private static final long serialVersionUID = 4083406832930707444L;

    private Document searchInput;
    private DefaultListModel searchResult;
    private PersonService personService;

    public SearchPersonAction(Document searchInput,
            DefaultListModel searchResult, PersonService personService) {
        this.searchInput = searchInput;
        this.searchResult = searchResult;
        this.personService = personService;
    }

    public void actionPerformed(ActionEvent e) {
        String searchString = getSearchString();

        List<Person> matchedPersons = personService.searchPersons(searchString);

        searchResult.clear();
        for (Person person : matchedPersons) {
            Object elementModel = new PersonElementModel(person);
            searchResult.addElement(elementModel);
        }
    }

    private String getSearchString() {
        try {
            return searchInput.getText(0, searchInput.getLength());
        } catch (BadLocationException e) {
            return null;
        }
    }

}

class PersonElementModel {

    private Person person;

    public PersonElementModel(Person person) {
        this.person = person;
    }

    public Person getPerson() {
        return person;
    }

    @Override
    public String toString() {
        return person.getFirstName() + ", " + person.getLastName();
    }
}

interface PersonService {

    List<Person> searchPersons(String searchString);
}

class Person {

    private int id;
    private String firstName;
    private String lastName;

    public Person(int id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public int getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

}

class PersonServiceMock implements PersonService {

    private List<Person> personDB;

    public PersonServiceMock() {
        personDB = new ArrayList<Person>();
        personDB.add(new Person(1, "Graham", "Parrish"));
        personDB.add(new Person(2, "Daniel", "Hendrix"));
        personDB.add(new Person(3, "Rachel", "Holman"));
        personDB.add(new Person(4, "Sarah", "Todd"));
        personDB.add(new Person(5, "Talon", "Wolf"));
        personDB.add(new Person(6, "Josephine", "Dunn"));
        personDB.add(new Person(7, "Benjamin", "Hebert"));
        personDB.add(new Person(8, "Lacota", "Browning "));
        personDB.add(new Person(9, "Sydney", "Ayers"));
        personDB.add(new Person(10, "Dustin", "Stephens"));
        personDB.add(new Person(11, "Cara", "Moss"));
        personDB.add(new Person(12, "Teegan", "Dillard"));
        personDB.add(new Person(13, "Dai", "Yates"));
        personDB.add(new Person(14, "Nora", "Garza"));
    }

    public List<Person> searchPersons(String searchString) {
        List<Person> matches = new ArrayList<Person>();

        if (searchString == null) {
            return matches;
        }

        for (Person person : personDB) {
            if (person.getFirstName().contains(searchString)
                    || person.getLastName().contains(searchString)) {
                matches.add(person);
            }

        }
        return matches;
    }

}

4
Ich mag diese Antwort +1, um zu erwähnen, Actionda Controllerich denke, dass alle EventListenerController sind ..
Nachokk

@nachokk Ja, in der Tat. Wie gesagt A controller encapsulates the application code that is executed in order to an user interaction. Das Bewegen der Maus, das Klicken auf eine Komponente, das Drücken einer Taste usw. sind alles Benutzerinteraktionen. Um es klarer zu machen, habe ich meine Antwort aktualisiert.
René Link

2

Sie können ein Modell in einer separaten, einfachen Java-Klasse und einen Controller in einer anderen erstellen.

Dann können Sie Swing-Komponenten darüber haben. JTablewäre eine der Ansichten (und das Tabellenmodell wäre de facto Teil der Ansicht - es würde nur vom "gemeinsamen Modell" in übersetzt JTable).

Immer wenn die Tabelle bearbeitet wird, weist ihr Tabellenmodell den "Hauptcontroller" an, etwas zu aktualisieren. Der Controller sollte jedoch nichts über die Tabelle wissen. Der Anruf sollte also eher so aussehen: updateCustomer(customer, newValue)nicht updateCustomer(row, column, newValue).

Fügen Sie eine Listener- (Beobachter-) Schnittstelle für das gemeinsam genutzte Modell hinzu. Einige Komponenten (z. B. Ihre Tabelle) können dies direkt implementieren. Ein anderer Beobachter könnte der Controller sein, der die Verfügbarkeit von Tasten usw. koordiniert.


Das ist eine Möglichkeit, aber natürlich können Sie es vereinfachen oder erweitern, wenn es für Ihren Anwendungsfall ein Overkill ist.

Sie können den Controller mit dem Modell zusammenführen und dieselben Klassenprozessaktualisierungen durchführen sowie die Verfügbarkeit der Komponenten aufrechterhalten. Sie können das "gemeinsam genutzte Modell" sogar zu einem machen TableModel(wenn es jedoch nicht nur von der Tabelle verwendet wird, würde ich empfehlen, zumindest eine benutzerfreundlichere API bereitzustellen, die keine Tabellenabstraktionen verliert).

Auf der anderen Seite können Sie komplexe Schnittstellen für Updates haben ( CustomerUpdateListener, OrderItemListener, OrderCancellationListener) und dedizierte Controller (oder Vermittler) nur für die Koordinierung der verschiedenen Ansichten.

Es hängt davon ab, wie kompliziert Ihr Problem ist.


Etwa 90% aller Ansichten bestehen aus einer Tabelle, in der der Benutzer ein zu bearbeitendes Element auswählen kann. Was ich bis jetzt getan habe, ist, dass ich ein Datenmodell habe, das alle CRUD-Operationen durchläuft. Ich verwende ein TableModel, um das Datenmodell an die JTable anzupassen. Um ein Element zu aktualisieren, würde ich table.getModel (). GetModel (). Update (Element e) aufrufen. Mit anderen Worten, die JTable-Art ist derzeit der Controller. Alle Schaltflächenaktionen werden in separate Klassen eingeteilt (ich verwende sie in verschiedenen Kontexten) und erledigen ihre Arbeit mit den Methoden des zugrunde liegenden Modells. Ist das ein tragfähiges Design?
Sbrattla

1

Für eine ordnungsgemäße Trennung verfügen Sie normalerweise über eine Controller-Klasse, an die die Frame-Klasse delegiert. Es gibt verschiedene Möglichkeiten, die Beziehungen zwischen den Klassen einzurichten. Sie können einen Controller implementieren und um Ihre Hauptansichtsklasse erweitern oder eine eigenständige Controller-Klasse verwenden, die der Frame aufruft, wenn Ereignisse auftreten. Die Ansicht empfängt normalerweise Ereignisse von der Steuerung, indem eine Listener-Schnittstelle implementiert wird.

Manchmal sind ein oder mehrere Teile des MVC-Musters trivial oder so "dünn", dass es unnötige Komplexität hinzufügt, um sie zu trennen. Wenn Ihr Controller voll von einzeiligen Anrufen ist, kann die Verwendung in einer separaten Klasse das zugrunde liegende Verhalten verschleiern. Wenn sich beispielsweise alle Ereignisse, die Sie behandeln, auf ein TableModel beziehen und einfache Operationen zum Hinzufügen und Löschen sind, können Sie alle Tabellenmanipulationsfunktionen in diesem Modell implementieren (sowie die Rückrufe, die zum Anzeigen des Modells erforderlich sind) JTable). Es ist keine echte MVC, aber es vermeidet das Hinzufügen von Komplexität, wenn sie nicht benötigt wird.

Denken Sie jedoch daran, Ihre Klassen, Methoden und Pakete in JavaDoc zu implementieren, damit die Komponenten und ihre Beziehungen ordnungsgemäß beschrieben werden!


@AndyT Während die meisten Ihrer Erklärungen gut sind, habe ich ein Problem mit Ihren Ratschlägen zur Kombination des Modells mit dem Controller. Was ist, wenn ich plötzlich den Controller wechseln möchte? Jetzt stelle ich fest, dass Sie das Modell mit dem Controller gekoppelt haben und das Modell ebenfalls ändern müssen. Ihr Code ist nicht mehr erweiterbar. Egal wie kurz Ihr Controller ist, ich würde ihn nicht mit einem Modell kombinieren. oder eine Ansicht.
Dhruv Gairola

Ich würde nicht widersprechen - es hängt sehr von Ihrer Bewerbung ab. Wenn Ihr Modell nicht komplexer als ein List-Objekt ist und Ihr Controller nur Elemente hinzufügt und entfernt, ist das Erstellen von drei separaten Klassen (List-Modell, Controller und Adapter für Ihr Modell zur Arbeit mit der JTable) übertrieben. Es ist einfacher, es in dem unwahrscheinlichen Fall, dass Sie einen anderen Controller benötigen, umzugestalten, als Shim-Klassen für einen unbekannten zukünftigen Bedarf auszugeben.
AndyT

@AndyT stimmte zu, vielleicht ist dies der schnellste Weg, wenn Ihre Anwendung klein ist. Aus Gründen der Erweiterbarkeit (überlegen Sie, ob das Hinzufügen nicht vom selben Programmierer vorgenommen wird) kann dies jedoch als Nachteil wirken.
Dhruv Gairola

2
@AndyT: Ich weiß nicht, wie lange Sie Software entwickelt haben, aber Ihr Beitrag zeigt, dass Sie das KISS-Prinzip übernommen haben. Viel zu viele intelligente, aber unerfahrene Java-Entwickler bevorzugen Entwurfsmuster wie die Bibel (Entwurfsmuster sind kaum mehr als eine hochrangige Programmierung zum Ausschneiden und Einfügen). In den meisten Fällen dient ein puristischer Ansatz durch das Erstellen separater Controller- und Ansichtsklassen nur dazu, dass die Wartung durch andere als den ursprünglichen Entwickler zu einem Albtraum für Programme wird, die größer als ein paar hundert Codezeilen sind. Wenn Sie Zweifel haben, halten Sie es einfach, dumm!
Bit-Twiddler

1
@AndyT: Der Weg zur Erleuchtung ist gespickt mit Schlaglöchern, Schlangenölverkäufern und Selbstgerechten. Es gibt jedoch nichts Schöneres, als sich über einen längeren Zeitraum in der eigenen Defäkation suhlen zu müssen, um zu lehren, die Dinge einfach zu halten. An Designmustern ist nichts auszusetzen. Das Kennen von Entwurfsmustern ist jedoch nicht dasselbe wie das Kennen von Software-Design. Kein bahnbrechendes Softwareprodukt wurde jemals mit einem Kochbuchansatz entwickelt. Das Entwerfen von Hochleistungssoftware, die den Anforderungen entspricht und einfach zu warten ist, ist immer noch eine Kunstform, deren Beherrschung Jahre erfordert.
Bit-Twiddler


0

Wenn Sie ein Programm mit einer GUI entwickeln , MVC - Muster ist fast da , aber verschwommen.

Das Erkennen von Modell-, Ansichts- und Controller-Code ist schwierig und normalerweise nicht nur eine Refactor-Aufgabe.

Sie wissen, dass Sie es haben, wenn Ihr Code wiederverwendbar ist. Wenn Sie MVC korrekt implementiert haben, sollte es einfach sein, eine TUI oder eine CLI zu implementieren oder eine RWD oder ein Mobile First Design mit derselben Funktionalität . Es ist leicht zu sehen, wie es gemacht wird, als es tatsächlich zu tun, außerdem auf einem vorhandenen Code.

Tatsächlich finden Interaktionen zwischen Modell, Ansicht und Controller mithilfe anderer Isolationsmuster statt (als Beobachter oder Zuhörer).

Ich denke, dieser Beitrag erklärt es ausführlich anhand des direkten Nicht-MVC-Musters (wie Sie es bei einem tun werden Antworten ) bis zur endgültigen wiederverwendbaren Implementierung:

http://www.austintek.com/mvc/

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.