Шаблон MVC и SWING

Одним из шаблонов дизайна, которые я нахожу наиболее сложными для получения реального понимания «реальной жизни SWING», является шаблон MVC. Я прошел через несколько сообщений на этом сайте, которые обсуждают шаблон, но я все еще не чувствую, что у меня есть четкое представление о том, как использовать шаблон в моем (Java SWING) приложении.

Предположим, что у меня есть JFrame, который содержит таблицу, пару текстовых полей и несколько кнопок. Я бы, вероятно, использовал TableModel для «моста» JTable с базовой моделью данных. Тем не менее, все функции, ответственные за очистку полей, проверку полей, блокировки полей вместе с действиями кнопок, обычно идут непосредственно в JFrame. Однако не смешивает ли controller и представление с шаблоном?

Насколько я могу судить, мне удается получить шаблон MVC «правильно» при просмотре JTable (и модели), но все становится грязным, когда я смотрю на весь JFrame в целом.

Мне бы очень хотелось услышать, как идут дела по этому поводу. Как вы идете, когда вам нужно отображать таблицу, пару полей и некоторые кнопки для пользователя (используя шаблон MVC)?

Книга, которую я очень рекомендую вам для MVC в качелях, будет «Head First Design Patterns» Фримана и Фримана. У них есть очень подробное объяснение MVC.

Краткое содержание

  1. Вы пользователь – вы взаимодействуете с точкой зрения. Представление – это ваше окно к модели. Когда вы делаете что-то для представления (например, нажмите кнопку «Воспроизведение»), тогда представление сообщает controllerу, что вы сделали. Это задача диспетчера справиться с этим.

  2. Контроллер просит модель изменить свое состояние. Контроллер принимает ваши действия и интерпретирует их. Если вы нажмете на кнопку, это задача controllerа, чтобы выяснить, что это значит и как манипулировать моделью на основе этого действия.

  3. Контроллер может также попросить изменить вид. Когда controller получает действие из представления, ему может потребоваться изменить представление в результате. Например, controller может включать или отключать определенные кнопки или пункты меню в интерфейсе.

  4. Модель уведомляет вид, когда его состояние изменилось. Когда что-то меняется в модели, основанной либо на некоторых действиях, которые вы предприняли (например, на кнопке), либо на некоторых других внутренних изменениях (например, следующая песня в списке воспроизведения), модель уведомляет мнение о том, что его состояние изменилось.

  5. Представление запрашивает модель для состояния. В представлении отображается состояние, которое оно отображает непосредственно из модели. Например, когда модель уведомляет мнение о том, что новая песня начала воспроизводиться, представление запрашивает имя песни из модели и отображает ее. Представление может также запрашивать модель для состояния в результате того, что controller запрашивает некоторые изменения в представлении.

введите описание изображения здесь Источник (если вам интересно, что такое «кремовый controller», подумайте о печенье Oreo, а controller – это сливочный центр, вид которого является верхним бисквитом, а модель – нижним бисквитом).

Если вам интересно, вы можете скачать довольно интересную песню о шаблоне MVC здесь !

Одна из проблем, с которой вы можете столкнуться при программировании Swing, заключается в объединении streamа SwingWorker и EventDispatch с шаблоном MVC. В зависимости от вашей программы, вашему представлению или controllerу, возможно, придется расширять SwingWorker и переопределять метод doInBackground() котором размещается ресурсоемкая логика. Это может быть легко слито с типичным шаблоном MVC и характерно для приложений Swing.

EDIT # 1 :

Кроме того, важно рассматривать MVC как своего рода составной набор различных шаблонов. Например, ваша модель может быть реализована с использованием шаблона Observer (требуя, чтобы View был зарегистрирован как наблюдатель для модели), в то время как ваш controller может использовать шаблон страtagsи.

РЕДАКТИРОВАТЬ № 2 :

Я также хотел бы ответить конкретно на ваш вопрос. В представлении должны отображаться ваши кнопки таблицы и т. Д., Которые, очевидно, будут реализовывать ActionListener. В вашем actionPerformed() вы обнаруживаете событие и отправляете его связанному методу в controllerе (помните – в представлении содержится ссылка на controller). Поэтому, когда нажата кнопка, событие обнаруживается в представлении, отправленном методу controllerа, controller может непосредственно запросить, чтобы отключить кнопку или что-то в этом роде. Затем controller будет взаимодействовать и модифицировать модель (которая будет в основном иметь методы getter и setter, а некоторые другие – для регистрации и уведомления наблюдателей и т. Д.). Как только модель будет изменена, она будет вызывать обновление для зарегистрированных наблюдателей (это будет представление в вашем случае). Следовательно, представление теперь обновится.

Мне не нравится идея представления, которая была уведомлена моделью при изменении ее данных. Я бы делегировал эту функциональность controllerу. В этом случае, если вы измените логику приложения, вам не нужно вмешиваться в код представления. Задача представления предназначена только для компонентов компонентов + макет не что иное, как ничего. Макетирование в качелях уже является сложной задачей, почему она мешает логике приложений?

Моя идея MVC (с которой я сейчас работаю, насколько это хорошо):

  1. Вид самый тупой из трех. Он ничего не знает о controllerе и модели. Его забота – это только простетика и макет компонентов свинг-компонентов.
  2. Модель также немой, но не такой тупой, как вид. Он выполняет следующие функции.
    • а. когда один из его сеттеров вызывается controllerом, он будет уведомлять об этом своих слушателей / наблюдателей (как я уже сказал, я бы передал эту роль controllerу). Я предпочитаю SwingPropertyChangeSupport для достижения этого, поскольку он уже оптимизирован для этой цели.
    • б. функциональность взаимодействия с базами данных.
  3. Очень умный controller. Знает вид и модель очень хорошо. Контроллер имеет две функции:
    • а. Он определяет действие, которое будет выполняться, когда пользователь взаимодействует с ним.
    • б. Он слушает модель. Как и то, что я сказал, когда вызывается установщик модели, модель будет вызывать уведомление для controllerа. Задача controllerа – интерпретировать это уведомление. Возможно, это должно отражать изменение взгляда.

Образец кода

Вид :

Как я уже сказал, создание представления уже многословно, поэтому просто создайте свою собственную реализацию 🙂

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

Это идеальное решение для интерфейса трех для целей тестирования. Я только представил свою реализацию Model и Controller.

Модель :

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

Контроллер:

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

Main, где настроен 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); ... } } 

Шаблон MVC – это модель того, как можно структурировать пользовательский интерфейс. Поэтому он определяет 3 элемента Model, View, Controller:

  • Модель Модель – это абстракция того, что представлено пользователю. В качелях у вас есть дифференциация моделей gui и моделей данных. Модели GUI абстрагируют состояние компонента ui, например ButtonModel . Модели данных абстрактно структурированные данные, которые пользователь представляет пользователю, например TableModel .
  • Просмотр. Представление представляет собой компонент ui, который отвечает за представление данных пользователю. Таким образом, он отвечает за все зависящие от ui вопросы, такие как макет, рисование и т. Д. Например, JTable .
  • Контроллер Контроллер инкапсулирует код приложения, который выполняется для взаимодействия с пользователем (движение мыши, щелчок мышью, нажатие клавиши и т. Д.). Контроллерам может потребоваться ввод для их выполнения, и они выдают результат. Они читают свои данные от моделей и моделей обновлений в результате выполнения. Они также могут реструктурировать ui (например, заменить компоненты ui или показать полный новый вид). Однако они не должны знать о компиляторах ui, потому что вы можете инкапсулировать реструктуризацию в отдельный интерфейс, который вызывает только controller. При качании controller обычно реализуется ActionListener или Action .

пример

  • Красный = модель
  • Зеленый = вид
  • Синий = controller

введите описание изображения здесь

Когда Button нажата, она вызывает ActionListener . ActionListener зависит только от других моделей. Он использует некоторые модели в качестве входных данных, а другие – как результат или вывод. Это похоже на аргументы метода и возвращаемые значения. Модели уведомляют ui, когда они обновляются. Поэтому нет необходимости в логике controllerа знать компонент ui. Объекты модели не знают ui. Уведомление выполняется с помощью шаблона наблюдателя. Таким образом, объекты модели знают только, что есть кто-то, кто хочет получить уведомление, если модель изменится.

В java swing есть некоторые компоненты, которые также реализуют модель и controller. Например, javax.swing.Action . Он реализует модель ui (свойства: enablement, small icon, name и т. Д.) И является controllerом, потому что он расширяет ActionListener .

Подробное объяснение, пример приложения и исходный код : https://www.link-intersystems.com/blog/2013/07/20/the-mvc-pattern-implemented-with-java-swing/ .

Основы MVC менее чем за 240 строк:

 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 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 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 personDB; public PersonServiceMock() { personDB = new ArrayList(); 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 searchPersons(String searchString) { List matches = new ArrayList(); if (searchString == null) { return matches; } for (Person person : personDB) { if (person.getFirstName().contains(searchString) || person.getLastName().contains(searchString)) { matches.add(person); } } return matches; } } 

Вы можете создать модель в отдельном, обычном classе Java и controllerе в другом.

Тогда вы можете иметь компоненты Swing поверх этого. JTable будет одним из видов (и модель таблицы будет де-факто частью представления – она ​​будет переводиться только из «общей модели» в JTable ).

Всякий раз, когда таблица редактируется, ее модель таблицы сообщает «главному controllerу» что-то обновить. Однако controller ничего не должен знать о таблице. Таким образом, вызов должен выглядеть больше: updateCustomer(customer, newValue) , а не updateCustomer(row, column, newValue) .

Добавить интерфейс слушателя (наблюдателя) для общей модели. Некоторые компоненты (например, ваша таблица) могут реализовать его непосредственно. Другим наблюдателем может быть controller, который координирует доступность кнопок и т. Д.


Это один из способов сделать это, но, конечно, вы можете упростить или расширить его, если его перехитрить для вашего случая использования.

Вы можете объединить controller с моделью и иметь одни и те же обновления процесса процесса и поддерживать доступность компонентов. Вы даже можете сделать «общую модель» TableModel (хотя, если она используется не только в таблице, я бы рекомендовал по крайней мере предоставить более дружественный API, который не будет терять табличные абстракции)

С другой стороны, у вас могут быть сложные интерфейсы для обновлений ( CustomerUpdateListener , OrderItemListener , OrderCancellationListener ) и выделенный controller (или медиатор) только для координации разных видов.

Это зависит от того, насколько сложна ваша проблема.

Для правильного разделения вы обычно должны иметь class controllerа, которому должен был делегироваться class Frame. Существуют различные способы настройки отношений между classами – вы можете реализовать controller и расширить его с помощью основного classа представления или использовать автономный class controllerа, который обращается к Frame при возникновении событий. Вид обычно принимает события от controllerа, реализуя интерфейс прослушивателя.

Иногда одна или несколько частей шаблона MVC тривиальны или так «тонкие», что он добавляет излишнюю сложность, чтобы отделить их. Если ваш controller полон однострочных вызовов, наличие его в отдельном classе может привести к запутыванию основного поведения. Например, если все события, которые вы обрабатываете, связаны с TableModel и являются простыми операциями добавления и удаления, вы можете реализовать все функции управления таблицами в этой модели (а также обратные вызовы, необходимые для отображения в JTable). Это не правда, MVC, но это позволяет избежать сложностей, когда это не требуется.

Однако вы его реализуете, запомните JavaDoc своими classами, методами и пакетами, чтобы компоненты и их отношения были правильно описаны!

Я нашел несколько интересных статей о реализации шаблонов MVC, которые могут решить вашу проблему.

Если вы разрабатываете программу с графическим интерфейсом , шаблон mvc почти там, но размыт.

Отключение модели, просмотра и кода controllerа затруднено, и обычно это не только задача рефакторинга.

Вы знаете, что у вас это есть, когда ваш код можно использовать повторно. Если вы правильно реализовали MVC, должно быть легко реализовать TUI или CLI или RWD или первый мобильный проект с одинаковой функциональностью. Это легко увидеть, чем сделать это на самом деле, причем на существующий код.

Фактически, взаимодействие между моделью, представлением и controllerом происходит с использованием других шаблонов изоляции (как Observer или Listener)

Я предполагаю, что этот пост объясняет это подробно, от прямого шаблона не MVC (как вы это сделаете на Q & D ) до окончательной реализации многократного использования:

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

  • Печать JPanel с прокручиваемым Jtable На нем
  • Рисование простого линейного графика в Java
  • Как включить базу данных SQLite в исполняемый Jar?
  • java thread.sleep ставит swing ui тоже
  • как перетаскивать файлы из каталога в java
  • Как установить AUTO-SCROLLING JTextArea в Java GUI?
  • Перетаскивание пользовательского объекта из JList в JLabel
  • Коробка масштабирования для области вокруг местоположения мыши на экране
  • Как получить индекс X и Y элемента внутри GridLayout?
  • Как установить DPI приложений Java Swing в Windows / Linux?
  • StackOverflowError при сериализации объекта в Java
  • Interesting Posts

    Внедрение bean-компонентов в class вне контекста Spring

    Вызов метода библиотеки .net из vba

    Как установить последнюю версию opensl Mac OS X El Capitan

    Как отсортировать treemap на основе его значений?

    Динамический анонимный тип в Razor вызывает RuntimeBinderException

    jersey rest web Service с интеграцией промежуточного ПО Activemq

    Как программно добавить элемент подменю в новую библиотеку поддержки Android

    Могу ли я загрузить UIImage из URL-адреса?

    создание параметризованных представлений в oracle11g

    Разница между «этими» и «супер» ключевыми словами в Java

    Определите, является ли String целым числом в Java

    Есть ли способ проверить, содержат ли две коллекции одни и те же элементы, независимо от порядка?

    Как выполнить тестирование объекта с запросами базы данных

    Как найти MAC-адреса незащищенной сети в Windows, используя самые простые шаги?

    Как контролировать использование компьютера, памяти и диска компьютера в Java?

    Давайте будем гением компьютера.