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?
źródło
Odpowiedzi:
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.
Ź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ąpienia
doInBackground()
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.źródło
=D
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:
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); ... } }
źródło
Wzorzec MVC to model struktury interfejsu użytkownika. Dlatego definiuje 3 elementy Model, Widok, Kontroler:
Przykład
Po
Button
kliknięciu wywołujeActionListener
. PlikActionListener
Zależ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; } }
źródło
Action
boController
w rzeczywistości myślę, że wszyscyEventListener
są kontrolerami ..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ź.Możesz stworzyć model w oddzielnej, zwykłej klasie Java, a kontroler w innej.
Wtedy możesz mieć do tego komponenty Swing.
JTable
byłby jednym z widoków (a model tabeli byłby de facto częścią widoku - przekładałby się tylko z „modelu współdzielonego” naJTable
).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)
nieupdateCustomer(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.
źródło
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!
źródło
Znalazłem kilka interesujących artykułów na temat implementacji wzorców MVC, które mogą rozwiązać twój problem.
źródło
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/
źródło