Wzorzec MVC i Swing

80

Jednym z wzorców projektowych, który jest dla mnie najtrudniejszy do zrozumienia w „prawdziwym życiu Swinga”, jest wzór MVC. Przeszedłem przez kilka postów w tej witrynie, które omawiają wzorzec, ale nadal nie czuję, że dobrze rozumiem, jak wykorzystać wzorzec w mojej aplikacji Java Swing.

Powiedzmy, że mam JFrame, który zawiera tabelę, kilka pól tekstowych i kilka przycisków. Prawdopodobnie użyłbym TableModel do „pomostowania” JTable z bazowym modelem danych. Jednak wszystkie funkcje odpowiedzialne za czyszczenie pól, walidację pól, blokowanie pól wraz z działaniami przycisków zwykle trafiałyby bezpośrednio do JFrame. Jednak czy to nie łączy kontrolera i widoku wzorca?

O ile widzę, udaje mi się „poprawnie” zaimplementować wzorzec MVC, patrząc na JTable (i model), ale sprawy stają się mętne, gdy patrzę na całą JFrame jako całość.

Naprawdę chciałbym usłyszeć, jak inni sobie z tym radzą. Jak postępujesz, gdy musisz wyświetlić tabelę, kilka pól i kilka przycisków użytkownikowi za pomocą wzorca MVC?

sbrattla
źródło
2
Oto powiązany przykład .
trashgod
Dla każdego, kto przyjdzie na tę imprezę - Swing NIE jest czystym MVC - zapożycza wiele z koncepcji, ale „
zrywa

Odpowiedzi:

106

Książką, którą gorąco polecam dla MVC in swing byłaby „Head First Design Patterns” autorstwa Freemana i Freemana. Mają bardzo obszerne wyjaśnienie MVC.

Krótkie podsumowanie

  1. Jesteś użytkownikiem - wchodzisz w interakcję z widokiem. Widok jest Twoim oknem na model. Kiedy robisz coś z widokiem (na przykład kliknij przycisk Odtwórz), widok informuje kontroler, co zrobiłeś. Zajęcie się tym należy do kontrolera.

  2. Sterownik prosi model o zmianę jego stanu. Kontroler podejmuje Twoje działania i je interpretuje. Jeśli klikniesz przycisk, zadaniem kontrolera jest ustalenie, co to oznacza i jak należy manipulować modelem w oparciu o tę akcję.

  3. Administrator może również poprosić o zmianę widoku. Gdy kontroler otrzyma akcję z widoku, może być konieczne poinformowanie widoku o zmianie w wyniku. Na przykład kontroler może włączać lub wyłączać określone przyciski lub elementy menu w interfejsie.

  4. Model powiadamia widok o zmianie jego stanu. Kiedy coś się zmieni w modelu, w oparciu o jakąś czynność, którą wykonałeś (np. Kliknięcie przycisku) lub inną wewnętrzną zmianę (np. Rozpoczął się następny utwór z listy odtwarzania), model powiadamia widok o zmianie jego stanu.

  5. Widok prosi model o stan. Widok otrzymuje stan, w jakim jest wyświetlany, bezpośrednio z modelu. Na przykład, gdy model powiadamia widok o rozpoczęciu odtwarzania nowej piosenki, widok żąda nazwy utworu od modelu i wyświetla ją. Widok może również poprosić model o stan w wyniku zażądania przez kontrolera jakiejś zmiany w widoku.

wprowadź opis obrazu tutaj Źródło (jeśli zastanawiasz się, czym jest „kremowy kontroler”, pomyśl o ciastku Oreo, w którym kontroler jest kremowym środkiem, widok to górny herbatnik, a model to dolny herbatnik).

Hm, w przypadku jesteś zainteresowany, można pobrać dość zabawny piosenkę o wzorzec MVC z tutaj !

Jednym z problemów, które możesz napotkać podczas programowania Swing, jest połączenie wątków SwingWorker i EventDispatch ze wzorcem MVC. W zależności od programu widok lub kontroler może wymagać rozszerzenia SwingWorker i zastąpieniadoInBackground() metody, w której umieszczana jest logika wymagająca dużych zasobów. Można to łatwo połączyć z typowym wzorem MVC i jest to typowe dla aplikacji Swing.

EDYCJA NR 1 :

Ponadto ważne jest, aby traktować MVC jako rodzaj kompozytu różnych wzorców. Na przykład model można zaimplementować przy użyciu wzorca Observer (wymagającego zarejestrowania widoku jako obserwatora dla modelu), podczas gdy kontroler może używać wzorca strategii.

EDYCJA # 2 :

Chciałbym dodatkowo odpowiedzieć konkretnie na Twoje pytanie. Powinieneś wyświetlić przyciski swojej tabeli itp. W Widoku, co oczywiście zaimplementowałoby ActionListener. W swojej actionPerformed()metodzie wykrywasz zdarzenie i wysyłasz je do powiązanej metody w kontrolerze (pamiętaj - widok zawiera odniesienie do kontrolera). Tak więc po kliknięciu przycisku zdarzenie jest wykrywane przez widok, wysyłane do metody kontrolera, kontroler może bezpośrednio poprosić widok o wyłączenie przycisku lub coś w tym stylu. Następnie kontroler będzie wchodził w interakcję z modelem i modyfikował go (który będzie miał głównie metody pobierające i ustawiające oraz kilka innych do rejestracji i powiadamiania obserwatorów i tak dalej). Jak tylko model zostanie zmodyfikowany, wywoła aktualizację zarejestrowanych obserwatorów (będzie to widok w twoim przypadku). W związku z tym widok zaktualizuje się teraz.

Dhruv Gairola
źródło
Czytałem książkę, ale trudno mi było zastosować wzór do SWINGU. Przeczytałem również w kilku miejscach, w których czytałem, że JFrame również może być postrzegany jako reprezentujący widok i kontroler.
sbrattla
... JFrame to komponent, a nie liść. zazwyczaj aktualizacje dokonywane przez kontroler są wysyłane do JFrame, który zajmuje się resztą, co może dawać złudzenie, że jest kontrolerem, ale w rzeczywistości tak nie jest, ponieważ nie zmienił modelu, tylko widok. jeśli JFrame w jakiś sposób bezpośrednio zmienił model - robisz to źle.
Dhruv Gairola
... znowu słowo kluczowe to „bezpośrednio”. w twoim przypadku możesz posłuchać kliknięć myszką na stole i wysłać logikę do metod w kontrolerze, które modyfikują model tabeli.
Dhruv Gairola
2
@DhruvGairola Drugi opis punktu dotyczy trzeciego punktu, trzeci i punkty mają takie same zduplikowane opisy. Czy możesz je poprawić, proszę.
Tryb Naruto Biju,
Ta piosenka to klasyka! =D
aaiezza
36

Nie podoba mi się pomysł, że widok jest tym, który jest powiadamiany przez model o zmianie danych. Oddelegowałbym tę funkcjonalność do kontrolera. W takim przypadku, jeśli zmienisz logikę aplikacji, nie musisz ingerować w kod widoku. Zadaniem widoku są tylko komponenty aplikacji + układ nic dodać nic ująć. Układanie w ruchu jest już wyczerpującym zadaniem, po co kolidować z logiką aplikacji?

Mój pomysł na MVC (z którym obecnie pracuję, jak na razie dobry) to:

  1. Widok jest najgłupszy z trzech. Nie wie nic o kontrolerze i modelu. Zajmuje się tylko protetyką i układem elementów huśtawki.
  2. Model jest również głupi, ale nie tak głupi jak widok. Realizuje następujące funkcje.
    • za. gdy jeden z jego ustawiaczy zostanie wywołany przez kontroler, wyśle ​​powiadomienie do swoich słuchaczy / obserwatorów (jak powiedziałem, przekazałbym tę rolę kontrolerowi). Wolę SwingPropertyChangeSupport, aby to osiągnąć, ponieważ jest już zoptymalizowany do tego celu.
    • b. funkcjonalność interakcji z bazą danych.
  3. Bardzo inteligentny kontroler. Zna bardzo dobrze widok i model. Kontroler posiada dwie funkcjonalności:
    • za. Definiuje akcję, którą widok wykona, gdy użytkownik wejdzie w interakcję z nim.
    • b. Słucha modelu. Tak jak powiedziałem, kiedy wywoływany jest ustawiacz modelu, model wyśle ​​powiadomienie do kontrolera. Zinterpretowanie tego powiadomienia należy do administratora. Może być konieczne odzwierciedlenie zmiany widoku.

Przykład kodu

Widok :

Jak powiedziałem, tworzenie widoku jest już pełne, więc po prostu stwórz własną implementację :)

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

Idealnie jest połączyć te trzy elementy do celów testowania. Udostępniłem tylko moją implementację Modelu i Kontrolera.

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
    ...
}

Kontroler:

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
    }
}

Strona główna, w której skonfigurowano MVC:

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);
        ...
    }
}
Bnrdo
źródło
4
Interesujące, ale jest mniej wydajne, gdy model pojedynczej encji jest wyświetlany w wielu widokach ... Wtedy twój projekt może prowadzić do „dużego kontrolera” obsługującego pojedynczy model, ale zarządzającego wszystkimi powiązanymi widokami. Sprawa staje się jeszcze trudniejsza, jeśli spróbujesz ponownie użyć zestawu „małego modelu”, dzięki agregacji w „duży model”, ponieważ widok wyświetla informacje wysłane w wielu elementach „małego modelu”.
Yves Martin
1
@onepotato Właśnie wypróbowałem twoje kody. Po naciśnięciu przycisku mogę uruchomić kody w funkcji setUpViewEvents (). Jednak gdy robię model.setSomething (123), kody w propertyChange nie są uruchamiane. Umieściłem nawet println bezpośrednio pod Object newVal = evt.getNewValue (); i nie drukuje.
AmuletxHeart
10
To NIE jest wzorzec architektoniczny MVC , ale ściśle powiązany wzorzec MVP (Model-View-Presenter). W typowym MVC zadaniem modelu jest właśnie powiadamianie widoku o zmianie , dokładnie tego, czego „nie lubisz”. Spójrz na ten diagram, aby zobaczyć, jak działają interakcje w typowym MVC.
MaxAxeHax
25

Wzorzec MVC to model struktury interfejsu użytkownika. Dlatego definiuje 3 elementy Model, Widok, Kontroler:

  • Model Model to abstrakcja czegoś, co jest prezentowane użytkownikowi. W toku masz rozróżnienie modeli GUI i modeli danych. Modele GUI wyodrębniają stan składnika interfejsu użytkownika, takiego jak ButtonModel . Modele danych abstrakcyjne dane strukturalne, które interfejs użytkownika przedstawia użytkownikowi, takie jak TableModel .
  • Widok Widok to komponent interfejsu użytkownika odpowiedzialny za prezentację danych użytkownikowi. W ten sposób jest odpowiedzialny za wszystkie kwestie zależne od interfejsu użytkownika, takie jak układ, rysunek, itp. Np . JTable .
  • Kontroler Kontroler hermetyzuje kod aplikacji, który jest wykonywany w celu interakcji użytkownika (ruch myszy, kliknięcie myszą, naciśnięcie klawisza itp.). Kontrolery mogą potrzebować danych wejściowych do wykonania i generują dane wyjściowe. Czytają swoje dane wejściowe z modeli i aktualizują modele w wyniku wykonania. Mogą również zmienić strukturę interfejsu użytkownika (np. Wymienić komponenty interfejsu użytkownika lub pokazać zupełnie nowy widok). Jednak nie mogą wiedzieć o komponentach interfejsu użytkownika, ponieważ restrukturyzację można zawrzeć w oddzielnym interfejsie, który tylko wywołuje kontroler. W ruchu kontroler jest zwykle implementowany przez ActionListener lub Action .

Przykład

  • Czerwony = model
  • Zielony = widok
  • Niebieski = kontroler

wprowadź opis obrazu tutaj

Po Buttonkliknięciu wywołuje ActionListener. PlikActionListenerZależy tylko od innych modeli. Wykorzystuje niektóre modele jako dane wejściowe, a inne jako wynik lub dane wyjściowe. To jak argumenty metod i zwracane wartości. Modele powiadamiają interfejs użytkownika o aktualizacji. Nie ma więc potrzeby, aby logika kontrolera znała komponent interfejsu użytkownika. Obiekty modelu nie znają interfejsu użytkownika. Powiadomienie odbywa się według wzorca obserwatora. W ten sposób obiekty modelu wiedzą tylko, że jest ktoś, kto chce otrzymać powiadomienie, jeśli model się zmieni.

W Java Swing jest kilka komponentów, które implementują również model i kontroler. Np. Javax.swing.Action . Implementuje model interfejsu użytkownika (właściwości: włączanie, mała ikona, nazwa itp.) I jest kontrolerem, ponieważ rozszerza ActionListener .

Szczegółowe wyjaśnienie, przykład zastosowania i kod źródłowy : https://www.link-intersystems.com/blog/2013/07/20/the-mvc-pattern-implemented-with-java-swing/ .

Podstawy MVC w mniej niż 260 liniach:

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultListModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.WindowConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.PlainDocument;

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;
    }
}

Prezentacja ekranowa MVC Basics

René Link
źródło
4
Podoba mi się ta odpowiedź +1, żeby wspomnieć, Actionbo Controllerw rzeczywistości myślę, że wszyscy EventListenersą kontrolerami ..
nachokk
@nachokk Tak, rzeczywiście. Jak powiedziałem A controller encapsulates the application code that is executed in order to an user interaction. Poruszanie myszą, klikanie komponentu, naciśnięcie klawisza itp. To wszystkie interakcje użytkownika. Aby było jaśniej, zaktualizowałem swoją odpowiedź.
René Link
2

Możesz stworzyć model w oddzielnej, zwykłej klasie Java, a kontroler w innej.

Wtedy możesz mieć do tego komponenty Swing. JTablebyłby jednym z widoków (a model tabeli byłby de facto częścią widoku - przekładałby się tylko z „modelu współdzielonego” na JTable).

Za każdym razem, gdy tabela jest edytowana, jej model tabeli mówi „głównemu kontrolerowi”, aby coś zaktualizował. Jednak kontroler nie powinien nic wiedzieć o tabeli. Więc wywołanie powinno wyglądać bardziej jak:, updateCustomer(customer, newValue)nie updateCustomer(row, column, newValue).

Dodaj interfejs nasłuchiwania (obserwatora) dla modelu udostępnionego. Niektóre komponenty (np. Stół) mogą to zaimplementować bezpośrednio. Innym obserwatorem może być kontroler koordynujący dostępność przycisków itp.


To jeden ze sposobów, aby to zrobić, ale oczywiście możesz go uprościć lub rozszerzyć, jeśli jest to przesada w twoim przypadku użycia.

Możesz połączyć kontroler z modelem i mieć tę samą klasę aktualizacji procesów i zachować dostępność komponentów. Możesz nawet uczynić „model współdzielony” a TableModel(chociaż jeśli nie jest używany tylko przez tabelę, zalecałbym przynajmniej zapewnienie bardziej przyjaznego interfejsu API, który nie wycieka abstrakcji tabel)

Z drugiej strony, można mieć kompleksowe interfejsy dla aktualizacji ( CustomerUpdateListener, OrderItemListener,OrderCancellationListener ) oraz dedykowany kontroler (lub pośrednik) tylko za koordynację różnych widoków.

To zależy od tego, jak skomplikowany jest Twój problem.

Konrad Garus
źródło
Około 90% wszystkich widoków składa się z tabeli, w której użytkownik może wybrać element do edycji. Do tej pory mam model danych, przez który przechodzą wszystkie operacje CRUD. Używam TableModel, aby dostosować model danych do JTable. Tak więc, aby zaktualizować element, wywołałbym table.getModel (). GetModel (). Update (Element e). Innymi słowy, rodzaj JTable jest teraz kontrolerem. Wszystkie akcje przycisków są umieszczane w osobnych klasach (używam ich ponownie w różnych kontekstach) i wykonuję swoją pracę za pomocą metod modelu bazowego. Czy to wykonalny projekt?
sbrattla
1

Aby zapewnić właściwą separację, zwykle masz klasę kontrolera, do której delegowałaby klasa Frame. Istnieją różne sposoby konfigurowania relacji między klasami - można zaimplementować kontroler i rozszerzyć go o klasę widoku głównego lub użyć autonomicznej klasy kontrolera, którą Frame wywołuje, gdy wystąpią zdarzenia. Widok zazwyczaj odbierał zdarzenia z kontrolera przez implementację interfejsu nasłuchującego.

Czasami jedna lub więcej części wzorca MVC jest trywialna lub tak „cienka”, że ich oddzielenie powoduje niepotrzebną złożoność. Jeśli twój kontroler jest pełen wywołań jednej linii, umieszczenie go w oddzielnej klasie może spowodować zaciemnienie podstawowego zachowania. Na przykład, jeśli wszystkie zdarzenia, które obsługujesz, są powiązane z TableModel i są prostymi operacjami dodawania i usuwania, możesz zaimplementować wszystkie funkcje manipulacji tabelami w tym modelu (jak również wywołania zwrotne niezbędne do wyświetlenia go w JTable). To nie jest prawdziwy MVC, ale unika dodawania złożoności tam, gdzie nie jest to potrzebne.

Jakkolwiek ją zaimplementujesz, pamiętaj o JavaDoc swoich klasach, metodach i pakietach, aby komponenty i ich relacje były odpowiednio opisane!

AndyT
źródło
@AndyT Chociaż większość twoich wyjaśnień jest dobra, mam problem z twoją radą dotyczącą łączenia modelu z kontrolerem. co jeśli chcę nagle zmienić kontroler? teraz stwierdzam, że połączyłeś model ze sterownikiem i musisz również zmodyfikować model. Twój kod nie jest już rozszerzalny. jakkolwiek krótki jest twój kontroler, nie połączyłbym go z modelem. lub widok.
Dhruv Gairola,
Nie zaprzeczyłbym - to zależy w dużej mierze od twojego podania. Jeśli twój model nie jest bardziej wyrafinowany niż obiekt List, a twój kontroler robi niewiele więcej niż dodawanie i usuwanie elementów, utworzenie trzech oddzielnych klas (model listy, kontroler i adapter dla twojego modelu do pracy z JTable) jest przesadą. W mało prawdopodobnym przypadku, gdy potrzebny jest inny kontroler, łatwiej jest go zreformować, niż wypuszczać klasy podkładek ze względu na nieznaną przyszłą potrzebę.
AndyT,
@AndyT zgodził się, być może, jeśli Twoja aplikacja jest mała, może to być najszybszy sposób. ale ze względu na rozszerzalność (rozważ, czy dodatek nie jest dokonywany przez tego samego programistę), może to działać jako wada.
Dhruv Gairola,
2
@AndyT: Nie wiem, jak długo tworzysz oprogramowanie, ale Twój post pokazuje, że przyjąłeś zasadę KISS. Zbyt wielu inteligentnych, ale niedoświadczonych programistów Java przyjmuje wzorce projektowe, takie jak biblia (wzorce projektowe to niewiele więcej niż programowanie typu „wytnij i wklej” na wysokim poziomie). W większości przypadków purystyczne podejście polegające na budowaniu oddzielnych klas kontrolera i widoku służy jedynie temu, aby konserwacja dokonywana przez kogokolwiek innego niż pierwotny programista stała się koszmarem dla programów zawierających więcej niż kilkaset wierszy kodu. W razie wątpliwości, Keep It Simple, Gupid!
bit-twiddler
1
@AndyT: Ścieżka do oświecenia jest pełna dziur, sprzedawców oleju wężowego i przekonanych o własnej nieomylności. Jednak nie ma to jak ciągłe pogrążanie się we własnej defekacji, aby nauczyć kogoś prostoty. Nie ma nic złego w wzorcach projektowych. Jednak znajomość wzorców projektowych to nie to samo, co znajomość projektowania oprogramowania. Żadne przełomowe oprogramowanie nie zostało kiedykolwiek zbudowane z wykorzystaniem metody książki kucharskiej. Projektowanie oprogramowania o wysokiej wydajności, które spełnia wymagania i jest łatwe w utrzymaniu, nadal jest formą sztuki, której opanowanie wymaga lat.
bit-twiddler
0

Jeśli tworzysz program z GUI , wzór mvc jest prawie na miejscu, ale jest rozmyty.

Wykrywanie modelu, widoku i kodu kontrolera jest trudne i zwykle nie jest tylko zadaniem refaktoryzacji.

Wiesz, że masz go, gdy kod nadaje się do ponownego wykorzystania. Jeśli poprawnie zaimplementowałeś MVC, powinno być łatwe do zaimplementowania TUI lub CLI lub RWD lub mobilną pierwszy projekt o takiej samej funkcjonalności. Łatwo to zobaczyć, niż zrobić to w rzeczywistości, a ponadto na istniejącym kodzie.

W rzeczywistości interakcje między modelem, widokiem i kontrolerem odbywają się przy użyciu innych wzorców izolacji (jak obserwator lub słuchacz)

Myślę, że ten post wyjaśnia to szczegółowo, od bezpośredniego wzorca innego niż MVC (tak jak zrobisz to na Q & D ) do ostatecznej implementacji wielokrotnego użytku:

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

albfan
źródło