2. Паттерн MVC
Last updated
Was this helpful?
Last updated
Was this helpful?
Паттерн MVC можно описать следующим образом
В данном определении под компонентом следует понимать часть кода (как правило, это отдельный класс), каждая из которых играет одну из ролей Контроллера, Модели или Представления, где Модель служит для извлечения и манипуляций данными приложения, Представление отвечает за видимое пользователю отображение этих данных, а Контроллер управляет всем этим оркестром
Архитектурная концепция (архитектурный паттерн) MVC позволяет разделить программу на три отдельных компонента, которые могут быть реализованы следующим образом:
1. Пользователь взаимодействует с представлением. Представление – «окно», через которое пользователь воспринимает модель. Когда вы делаете что-то с представлением (скажем, щелкаете на кнопке воспроизведения), представление сообщает контроллеру, какая операция была выполнена. Контроллер должен обработать это действие.
2. Контроллер обращается к модели с запросами об изменении состояния. Контроллер получает действия пользователя и интерпретирует их. Если вы щелкаете на кнопке, контроллер должен разобраться, что это значит и какие операции с моделью должны быть выполнены при данном действии.
3. Контроллер также может обратиться к представлению с запросами об изменении. Когда контроллер получает действие от представления, в результате его обработки он может обратиться к представлению с запросом на изменение (скажем, заблокировать некоторые кнопки или команды меню).
4. Модель оповещает представление об изменении состояния. Когда в модели что-то изменяется (вследствие действий пользователя или других внутренних изменений – скажем, перехода к следующей песне в списке), модель оповещает представление об изменении состояния.
5. Представление запрашивает у модели информацию состояния. Представление получает отображаемую информацию состояния непосредственно от модели. Например, когда модель оповещает представление о начале воспроизведения новой песни, представление запрашивает название песни и отображает его. Представление также может запросить у модели информацию состояния в результате запроса на изменение состояния со стороны контроллера.
Модель (Model) – содержит бизнес-логику приложения и включает методы выборки, обработки и предоставления конкретных данных, что зачастую делает ее очень «толстой», что вполне нормально. Модель не должна напрямую взаимодействовать с пользователем. Модель:
предоставляет (представлению и контроллеру):
данные;
методы работы с данными:
запросы к базам данных;
валидация данных;
бизнес-логика (если модель «активна»).
нуждается в следующем:
в представлении (не может самостоятельно демонстрировать данных и результаты их обработки);
в контроллере (не имеет точек взаимодействия с пользователем).
может иметь множество различных представлений и контроллеров;
отвечает на запросы изменением состояния. При этом, в модель может быть встроено автоматическое оповещение «наблюдателей».
Представление (View) – используется для задания внешнего вида отображения данных, полученных из контроллера и модели. Представление не должно обращаться к базе данных, этим должны заниматься модели. Также, представление не должно работать с данными, полученными из запроса пользователя. Эту задачу должен выполнять контроллер. Представление:
отвечает за получение необходимых данных от модели и отправку их пользователю;
не обрабатывает введенные данные пользователя;
может влиять на состояние модели через отправку ей сообщений (вызовы методов).
Контроллер (Controller) – связующее звено, соединяющее модели, представления и другие компоненты в рабочее приложение. Контроллер отвечает за обработку действий пользователя. В хорошо спроектированном MVC-приложении контроллеры обычно очень «тонкие» и содержат только несколько десятков строк кода. Логика контроллера довольно типична и большая ее часть выносится в базовые классы. Модели, наоборот, очень «толстые» и содержат большую часть кода, связанную с обработкой данных, так как структура данных и бизнес-логика, содержащаяся в них, обычно довольно специфична для конкретного приложения. Контроллер:
обеспечивает «связь» между пользователем и системой, контролирует и направляет:
данные от пользователя к системе;
реакцию системы – пользователю.
использует модель и представление для необходимого действия;
в случае «пассивной» модели – реализует бизнес-логику.
Рассмотрим пример реализации паттерна MVC на примере просто приложения. Приложение выводит на экран список студентов, добавляет нового студента и удаляет выделенного студента из списка.
Для начала, создадим новый JavaFX проект. Шаблон JavaFX проекта содержит три файла. Давайте определимся, к какой категории относится тот или иной файл.
Файл sample.fxml
, очевидно, относится к представлению (View). Он задает внешний вид приложения.
Класс Main
(файл Main.java
) относится к контроллеру. Этот класс можно назвать контроллером приложения. Он содержит метод Application.launch()
, который запускает JavaFX приложение, настраивается окно, создается объект сцены, устанавливается нужный fxml файл для генерации графа сцены для данного окна.
Класс Controller
(файл Controller.java
) очевидно является контроллером. Это контроллер окна, который содержит код для обработки событий, связанный с данным окном.
Создадим соответствующие пакеты и распределим файлы по своим пакетам. Не забудьте изменить путь к файлу fxml.
Теперь создадим класс Student
, который будет содержать данные и логику для объекта студента. Очевидно, что этот класс относится к классу модели.
Спроектируем UI для нашего приложения. Для этого отредактируем файл sample.fxml
. За вывод списка студентов будет отвечать элемент ListView
. Кроме него предусмотрены две кнопки для добавления и удаления студента.
Добавим обработчики нажатий на кнопки в контроллер окна Controller.java
.
Теперь вернемся к классам модели. Очевидно, что в данном приложении объекты класса Student
должны быть организованы в некоторую коллекцию. Кроме того, мы должны предоставить метод добавления и удаления студентов из коллекции.
Так как «сборка» отдельных частей приложения осуществляется в контроллере – организаторе и посреднике между различными компонентами приложения, самым простым вариантом было бы просто создать пустую коллекцию студентов, в которую можно было бы напрямую добавлять или удалять студентов.
Так как мы оставляем возможности для будущего расширения программы, в которой могут появиться новые окна (например, отдельное окно для добавления студента), коллекция со студентами должна быть доступна во всем приложении.
Таким образом, первый кандидат на хранении коллекции со студентами – контроллер приложения, класс Main
. Код для такого варианта будет примерно следующим:
Методы addStudent()
и deleteStudent()
будут вызываться из контроллера окна вместе с объектом для добавления или удаления.
Такой подход оправдан только, если мы точно знаем, что программа дальше не будет расширяться и наша модель будет, фактически, состоять из одного класса Student
. Но мы хотим оставить задел на будущее, когда у нас может появиться класс студенческой группы, класс с оценками, класс учебной дисциплины, данные могут записываться и считываться из файлов, передаваться по сети и так далее.
В случае расширения программы в контроллер приложения будут добавляться все новые и новые ссылки на различные объекты и коллекции, а также появляться новые методы для управления этими объектами и коллекциями.
Поэтому, лучшим вариантом будет применение паттерна проектирования «Фасад», который позволит перенести весь будущий функционал в слой модели.
Принцип работы паттерна «Фасад» можно объяснить следующим образом: фасад – это объект некоторого класса, который предоставляет простой (но урезанный) интерфейс работы со сложной системой объектов. Таким образом, если наша модель будет усложняться, и будут добавляться новые классы, то будет усложняться внутреннее строение фасада, а внешне он будет предоставлять те же самые простые методы для работы со сложной моделью.
Создадим класс StudentsFacade
. Класс будет содержать коллекцию со студентами, а также предоставлять публичные методы добавления и удаления студента.
Теперь создадим объект фасада в контроллере приложения и создадим методы, которые будут вызывать методы фасада.
Следующий шаг – необходимо написать обработчик кнопки «Добавить». При нажатии на кнопку необходимо создать объект класса Student
и вызвать метод Main.addStudent()
, которому необходимо передать созданный объект.
Так как обработчик нажатия на кнопку находится в контроллере окна, мы должны из контроллера окна (Controller.java
) вызвать метод контроллера окна (Main.java
). Чтобы это сделать, мы должны передать контроллеру окна ссылку на контроллер приложения.
Давайте разберемся, как это сделать. Для начала, откроем контроллер приложения и немного изменим загрузку fxml файла.
Если ранее мы использовали статический метод FXMLLoader.load()
, то теперь мы создадим объект класса FXMLLoader
, укажем в конструкторе ресурс fxml файла и вызовем метод load()
у созданного объекта.
Эти манипуляции нужны нам для того, чтобы вызвать у объекта метод getController()
, который вернет нам ссылку на созданный объект класса Controller
. Таким образом, мы получим ссылку на объект контроллера окна.
Далее, в контроллере окна необходимо добавить поле типа Main
и публичный метод, который будет записывать в поле переданную ссылку на объект контроллера приложения.
Таким образом, контроллер окна будет иметь ссылку на объект контроллера приложения. Теперь мы можем приступать к написанию кода для обработчика кнопки «Добавить». Для упрощения кода, будем генерировать значения полей для объекта студента.
Итак, на данный момент мы реализовали этапы 1 – 3 схемы работы паттерна MVC: пользователь взаимодействует с представлением (нажимает на кнопку «Добавить»), контроллер обращается к модели с запросами об изменении состояния. Но нам необходимо оповестить представление о том, что модель изменилась, чтобы представление обновилось (на экране появилась новая запись о студенте).
Для начала, реализуем взаимодействие модели и представления без использования встроенных средств JavaFX. Для этого нам необходимо разобраться с паттерном «Наблюдатель» (Observer).
Паттерн Observer определяет зависимость "один-ко-многим" между объектами так, что при изменении состояния одного объекта все зависящие от него объекты уведомляются и обновляются автоматически.
Паттерн определяет объект Subject, хранящий данные (модель) и объекты Observer, которые реализуют функциональность представлений. При создании наблюдатели Observer регистрируются у объекта Subject. Когда объект Subject изменяется, он извещает об этом всех зарегистрированных наблюдателей. После этого каждый обозреватель запрашивает у объекта Subject ту часть состояния, которая необходима для отображения данных.
Итак, реализуем этот паттерн в нашем исходном коде. В качестве Subject у нас выступает объект класса StudentsFacade
, который хранит нужные данные. Укажем в исходном коде, что класс StudentsFacade
наследуется от класса java.util.Observable
. Класс Observable
содержит готовый функционал для регистрации наблюдателей и для их оповещения об изменении модели. В классе StudentsFacade
есть два метода, которые меняют состояние объекта – метод addStudent()
добавляет новый объект студента в коллекцию, метод deleteStudent()
извлекает из коллекции объект студента. Таким образом, при вызове этих методов, мы должны оповестить всех наблюдателей о том, что произошло изменение данных и оповестить их об этом и передать нужные данные для отображения.
Далее необходимо указать, что используемый для вывода студентов элемент ListView
реализует интерфейс java.util.Observer
, а также необходимо запрограммировать реакцию на извещение об изменении модели.
Когда в классе StudentsFacade
будет выполняться метод addStudent()
или deleteStudent()
и выполнится команда notifyObservers()
, у объекта класса ListViewObservers
будет вызван метод update()
, в котором будет передано два параметра – ссылка на объект, который вызвал метод и переданные данные, если они есть (иногда необходимо передать в Observer сам факт изменений, тогда вызывается метод notifyObservers()
без параметров).
Мы проверяем – от кого произошло событие, и обновляем список с помощью пришедших данных (очищаем список от существующих элементов и добавляем новые).
Теперь необходимо заменить класс ListView
на ListViewObserver
. Регистрация Observer будет происходить в классе контроллера приложения
Нам осталось только реализовать функционал для кнопки «Удалить» и наше небольшое приложение теперь полностью готово.
Тестируем работу приложения.
Мы реализовали паттерн «Наблюдатель» вручную, но Java FX уже имеет готовый функционал для реализации этого паттерна. Для этого в JavaFX предусмотрен целый набор классов Observable*, которые предоставляют различные значения и коллекции уже со встроенной поддержкой паттерна Observer
. Давайте реализуем 4-5 этап паттерна MVC с помощью встроенных классов Observable
.
Для начала перейдем в класс StudentsFacade
и изменим класс коллекции с List
на ObservableList
. Классу StudentsFacade
теперь нет нужды наследоваться от класса java.util.Observable
, так как этот функционал теперь реализует непосредственно коллекция со студентами. Кроме того, добавим геттер для коллекции (хотя это не очень согласуется с принципом инкапсуляции).
В классе Main
заменим метод bindObserverToFacade()
на setItemsForListView()
. Обратите внимание, что элемент ListView
содержит метод setItems(),
который принимает на вход объект класса ObservableList
. При вызове метода регистрация и обновление спискового элемента происходит без нашего участия – мы просто должны вызвать метод setItems()
и передать ему ObservableList
.
Заменим использование ListViewObserver
на использование стандартного элемента ListView
и изменим содержимое класса
.Controller
Запустим приложение и убедимся, что его работа не изменилась, но мы избавились от необходимости создавать свой подкласс спискового элемента и вручную добавлять блок оповещения наблюдателей и вручную прописывать реакцию на оповещение об изменении данных.