# 2. Паттерн MVC



Паттерн MVC можно описать следующим образом

{% hint style="info" %}
Шаблон проектирования MVC предполагает разделение данных приложения, пользовательского интерфейса и управляющей логики на три отдельных компонента: Модель, Представление и Контроллер – таким образом, что модификация каждого компонента может осуществляться независимо.
{% endhint %}

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

Архитектурная концепция (архитектурный паттерн) MVC позволяет разделить программу на три отдельных компонента, которые могут быть реализованы следующим образом:

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MUNvT6d-d2ErrRtiInk%2F-MUNxdPB-hNLqQOHw2ik%2Fimage.png?alt=media\&token=f4c15830-1d01-445c-8472-29e9e6162e69)

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

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

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

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

5\.    **Представление запрашивает у модели информацию состояния**. Представление получает отображаемую информацию состояния непосредственно от модели. Например, когда модель оповещает представление о начале воспроизведения новой песни, представление запрашивает название песни и отображает его. Представление также может запросить у модели информацию состояния в результате запроса на изменение состояния со стороны контроллера.

**Модель (Model)** – содержит бизнес-логику приложения и включает методы выборки, обработки и предоставления конкретных данных, что зачастую делает ее очень «толстой», что вполне нормально. Модель не должна напрямую взаимодействовать с пользователем. **Модель:**

* предоставляет (представлению и контроллеру):
  * данные;
  * методы работы с данными:
    * запросы к базам данных;
    * валидация данных;
    * бизнес-логика (если модель «активна»).
* нуждается в следующем:
  * в представлении (не может самостоятельно демонстрировать данных и результаты их обработки);
  * в контроллере (не имеет точек взаимодействия с пользователем).
* может иметь множество различных представлений и контроллеров;
* отвечает на запросы изменением состояния. При этом, в модель может быть встроено автоматическое оповещение «наблюдателей».

**Представление (View)** – используется  для задания внешнего вида отображения данных, полученных из контроллера и модели. Представление не должно обращаться к базе данных, этим должны заниматься модели. Также, представление не должно работать с данными, полученными из запроса пользователя. Эту задачу должен выполнять контроллер. **Представление:**

* отвечает за получение необходимых данных от модели и отправку их пользователю;
* не обрабатывает введенные данные пользователя;
* может влиять на состояние модели через отправку ей сообщений (вызовы методов).

**Контроллер (Controller)** – связующее звено, соединяющее модели, представления и другие компоненты в рабочее приложение. Контроллер отвечает за обработку действий пользователя. В хорошо спроектированном MVC-приложении контроллеры обычно очень «тонкие» и содержат только несколько десятков строк кода. Логика контроллера довольно типична и б**о**льшая ее часть выносится в базовые классы. Модели, наоборот, очень «толстые» и содержат б**о**льшую часть кода, связанную с обработкой данных, так как структура данных и бизнес-логика, содержащаяся в них, обычно довольно специфична для конкретного приложения. **Контроллер:**

* обеспечивает «связь» между пользователем и системой, контролирует и направляет:
  * данные от пользователя к системе;
  * реакцию системы – пользователю.
* использует модель и представление для необходимого действия;
* в случае «пассивной» модели – реализует бизнес-логику.

Рассмотрим пример реализации паттерна MVC на примере просто приложения. Приложение выводит на экран список студентов, добавляет нового студента и удаляет выделенного студента из списка.

Для начала, создадим новый JavaFX проект. Шаблон JavaFX проекта содержит три файла. Давайте определимся, к какой категории относится тот или иной файл.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MV1pyLN5oJncGz9uHsI%2F-MV1q_oG49LnZ_hcaR6G%2Fimage.png?alt=media\&token=ae955ac6-e0ff-41aa-9ec8-05ff3a4dbdae)

Файл `sample.fxml`, очевидно, относится к представлению (**View**). Он задает внешний вид приложения.

Класс `Main` (файл `Main.java`) относится к контроллеру. Этот класс можно назвать контроллером приложения. Он содержит метод `Application.launch()`, который запускает JavaFX приложение, настраивается окно, создается объект сцены, устанавливается нужный fxml файл для генерации графа сцены для данного окна.

Класс `Controller` (файл `Controller.java`) очевидно является контроллером. Это контроллер окна, который содержит код для обработки событий, связанный с данным окном.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MV1pyLN5oJncGz9uHsI%2F-MV1qcFOGNhqmlVvANQB%2Fimage.png?alt=media\&token=8035733d-bc4f-4733-a7c3-747296d57c60)

Создадим соответствующие пакеты и распределим файлы по своим пакетам. Не забудьте изменить путь к файлу fxml.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MV1pyLN5oJncGz9uHsI%2F-MV1qdzbL4s0Pe7UanVb%2Fimage.png?alt=media\&token=b7e2767c-b9ea-475f-8613-881e27c99883)

Теперь создадим класс `Student`, который будет содержать данные и логику для объекта студента. Очевидно, что этот класс относится к классу **модели**.

{% code title="Student.java" %}
```java
public class Student {

    private String firstName;
    private String lastName;
    private String group;

    public Student(String firstName, String lastName, String group) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.group = group;
    }

    @Override
    public String toString() {
        return "Student{" +
                "firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                ", group='" + group + '\'' +
                '}';
    }
}

```
{% endcode %}

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MV1pyLN5oJncGz9uHsI%2F-MV1qlV2AgwMRuV1B66e%2Fimage.png?alt=media\&token=c126259d-e718-4522-98e7-313ce3bd171b)

Спроектируем UI для нашего приложения. Для этого отредактируем файл `sample.fxml`. За вывод списка студентов будет отвечать элемент `ListView`. Кроме него предусмотрены две кнопки для добавления и удаления студента.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MV1pyLN5oJncGz9uHsI%2F-MV1qp__6ye46Xg72LYK%2Fimage.png?alt=media\&token=5f36cd1d-7b20-4f35-9e11-57b4ad95c5ef)

Добавим обработчики нажатий на кнопки в контроллер окна `Controller.java`.

{% code title="Controller.java" %}
```java
public class Controller implements Initializable {

    @Override
    public void initialize(URL location, ResourceBundle resources) {
    }

    @FXML
    public void add(ActionEvent event) {
        // Обработчик кнопки "Добавить"
    }

    @FXML
    public void delete(ActionEvent event) {
        // Обработчик кнопки "Удалить"
    }
}
```
{% endcode %}

Теперь вернемся к классам модели. Очевидно, что в данном приложении объекты класса `Student` должны быть организованы в некоторую коллекцию. Кроме того, мы должны предоставить метод добавления и удаления студентов из коллекции.

Так как «сборка» отдельных частей приложения осуществляется в контроллере – организаторе и посреднике между различными компонентами приложения, самым простым вариантом было бы просто создать пустую коллекцию студентов, в которую можно было бы напрямую добавлять или удалять студентов.

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

Таким образом, первый кандидат на хранении коллекции со студентами – контроллер приложения, класс `Main`. Код для такого варианта будет примерно следующим:

{% code title="Main.java" %}
```java
public class Main extends Application {

    private List<Student> list;

    @Override
    public void init() throws Exception {
        list = new ArrayList<>();
    }

    public void addStudent(Student student) {
        list.add(student);
    }

    public void deleteStudent(Student student) {
        list.remove(student);
    }


    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }


    public static void main(String[] args) {
        launch(args);
    }
}
```
{% endcode %}

Методы `addStudent()` и `deleteStudent()` будут вызываться из контроллера окна вместе с объектом для добавления или удаления.

Такой подход оправдан только, если мы точно знаем, что программа дальше не будет расширяться и наша модель будет, фактически, состоять из одного класса `Student`. Но мы хотим оставить задел на будущее, когда у нас может появиться класс студенческой группы, класс с оценками, класс учебной дисциплины, данные могут записываться и считываться из файлов, передаваться по сети и так далее.

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

Поэтому, лучшим вариантом будет применение **паттерна проектирования «Фасад»**, который позволит перенести весь будущий функционал в слой модели.

Принцип работы паттерна «Фасад» можно объяснить следующим образом: фасад – это объект некоторого класса, который предоставляет простой (но урезанный) интерфейс работы со сложной системой объектов. Таким образом, если наша модель будет усложняться, и будут добавляться новые классы, то будет усложняться внутреннее строение фасада, а внешне он будет предоставлять те же самые простые методы для работы со сложной моделью.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MV1pyLN5oJncGz9uHsI%2F-MV2-zFAZ0bkIjoi42oG%2Fimage.png?alt=media\&token=89d03fba-17fb-410b-a795-bbb403c14d73)

Создадим класс `StudentsFacade`. Класс будет содержать коллекцию со студентами, а также предоставлять публичные методы добавления и удаления студента.

{% code title="StudentFacade.java" %}
```java
public class StudentFacade {
    private List<Student> studentList;

    public StudentFacade() {
        studentList = new ArrayList<>();
    }

    public void addStudent(Student student) {
        studentList.add(student);
    }

    public void deleteStudent(Student student) {
        studentList.remove(student);
    }
}
```
{% endcode %}

Теперь создадим объект фасада в контроллере приложения и создадим методы, которые будут вызывать методы фасада.

{% code title="Main.java" %}
```java
public class Main extends Application {

    private StudentFacade facade;

    @Override
    public void init() throws Exception {
        facade = new StudentFacade();
    }

    public void addStudent(Student student) {
        facade.addStudent(student);
    }

    public void deleteStudent(Student student) {
        facade.deleteStudent(student);
    }
    
    @Override
    public void start(Stage primaryStage) throws Exception{
        Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
        primaryStage.setTitle("Hello World");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}
```
{% endcode %}

&#x20;         Следующий шаг – необходимо написать обработчик кнопки «Добавить». При нажатии на кнопку необходимо создать объект класса `Student` и вызвать метод `Main.addStudent()`, которому необходимо передать созданный объект.

&#x20;         Так как обработчик нажатия на кнопку находится в контроллере окна, мы должны из контроллера окна (`Controller.java`) вызвать метод контроллера окна (`Main.java`). Чтобы это сделать, мы должны передать контроллеру окна ссылку на контроллер приложения.

&#x20;         Давайте разберемся, как это сделать. Для начала, откроем контроллер приложения и немного изменим загрузку fxml файла.

{% code title="Main.java" %}
```java
    @Override
    public void start(Stage primaryStage) throws Exception {

        FXMLLoader loader = new FXMLLoader(getClass().getResource("../view/sample.fxml"));
        Parent root = loader.load();

        primaryStage.setTitle("Студенты");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }
```
{% endcode %}

Если ранее мы использовали статический метод `FXMLLoader.load()`, то теперь мы создадим объект класса `FXMLLoader`, укажем в конструкторе ресурс fxml файла и вызовем метод `load()` у созданного объекта.

&#x20;         Эти манипуляции нужны нам для того, чтобы вызвать у объекта метод `getController()`, который вернет нам ссылку на созданный объект класса `Controller`. Таким образом, мы получим ссылку на объект контроллера окна.

&#x20;         Далее, в контроллере окна необходимо добавить поле типа `Main` и публичный метод, который будет записывать в поле переданную ссылку на объект контроллера приложения.

{% code title="Controller.java" %}
```java
public class Controller implements Initializable {

    private Main main;
    
    public void getMainController(Main main) {
        this.main = main;
    }
    
    ...
}
```
{% endcode %}

{% code title="Main.java" %}
```java
    @Override
    public void start(Stage primaryStage) throws Exception {

        FXMLLoader loader = new FXMLLoader(getClass().getResource("../view/sample.fxml"));
        Parent root = loader.load();

        Controller controller = loader.getController();
        controller.getMainController(this);
        
        primaryStage.setTitle("Студенты");
        primaryStage.setScene(new Scene(root, 300, 275));
        primaryStage.show();
    }
```
{% endcode %}

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MV1pyLN5oJncGz9uHsI%2F-MV21mIKX2C9SJPQbS4F%2Fimage.png?alt=media\&token=443abb9e-1afa-4d4f-9c0c-54f16549196a)

Таким образом, контроллер окна будет иметь ссылку на объект контроллера приложения. Теперь мы можем приступать к написанию кода для обработчика кнопки «Добавить». Для упрощения кода, будем генерировать значения полей для объекта студента.

{% code title="Controller.java" %}
```java
    @FXML
    public void add(ActionEvent event) {
        // Создаем объект студента
        Student student = new Student(
                String.valueOf(new Random().nextInt(100)),
                String.valueOf(new Random().nextInt(100)),
                String.valueOf(new Random().nextInt(100))
        );

        // Вызываем метод добавления студента
        main.addStudent(student);
    }
```
{% endcode %}

Итак, на данный момент мы реализовали этапы 1 – 3 схемы работы паттерна MVC: пользователь взаимодействует с представлением (нажимает на кнопку «Добавить»), контроллер обращается к модели с запросами об изменении состояния. Но нам необходимо оповестить представление о том, что модель изменилась, чтобы представление обновилось (на экране появилась новая запись о студенте).

Для начала, реализуем взаимодействие модели и представления без использования встроенных средств JavaFX. Для этого нам необходимо разобраться с паттерном **«Наблюдатель» (Observer)**.

## **Паттерн «Наблюдатель» (Observer)**

Паттерн **Observer** определяет зависимость "один-ко-многим" между объектами так, что при изменении состояния одного объекта все зависящие от него объекты уведомляются и обновляются автоматически.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MV1pyLN5oJncGz9uHsI%2F-MV2269P1jIBie0P6gAP%2Fimage.png?alt=media\&token=d998f629-c1e8-434c-b388-c0f2427eea15)

Паттерн определяет объект **Subject**, хранящий данные (модель) и объекты **Observer**, которые реализуют функциональность представлений. При создании наблюдатели **Observer** регистрируются у объекта **Subject**. Когда объект **Subject** изменяется, он извещает об этом всех зарегистрированных наблюдателей. После этого каждый обозреватель запрашивает у объекта **Subject** ту часть состояния, которая необходима для отображения данных.

Итак, реализуем этот паттерн в нашем исходном коде. В качестве **Subject** у нас выступает объект класса `StudentsFacade`, который хранит нужные данные. Укажем в исходном коде, что класс `StudentsFacade` наследуется от класса `java.util.Observable`. Класс `Observable` содержит готовый функционал для регистрации наблюдателей и для их оповещения об изменении модели. В классе `StudentsFacade` есть два метода, которые меняют состояние объекта – метод `addStudent()` добавляет новый объект студента в коллекцию, метод `deleteStudent()` извлекает из коллекции объект студента. Таким образом, при вызове этих методов, мы должны оповестить всех наблюдателей о том, что произошло изменение данных и оповестить их об этом и передать нужные данные для отображения.

{% code title="StudentFacade.java" %}
```java
public class StudentFacade extends Observable {
    private List<Student> studentList;

    public StudentFacade() {
        studentList = new ArrayList<>();
    }

    public void addStudent(Student student) {
        studentList.add(student);
        setChanged();
        notifyObservers(studentList);
    }
    public void deleteStudent(Student student) {
        studentList.remove(student);
        setChanged();
        notifyObservers(studentList);
    }
}
```
{% endcode %}

Далее необходимо указать, что используемый для вывода студентов элемент `ListView` реализует интерфейс `java.util.Observer`, а также необходимо запрограммировать реакцию на извещение об изменении модели.

Когда в классе `StudentsFacade` будет выполняться метод `addStudent()` или `deleteStudent()` и выполнится команда `notifyObservers()`, у объекта класса `ListViewObservers` будет вызван метод `update()`, в котором будет передано два параметра – ссылка на объект, который вызвал метод и переданные данные, если они есть (иногда необходимо передать в **Observer** сам факт изменений, тогда вызывается метод `notifyObservers()` без параметров).

```java
public class ListViewObserver extends ListView<Student> implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof StudentFacade) {
            // В параметре arg передан список для отображения
            List<Student> list = (List<Student>) arg;
            getItems().clear();
            getItems().addAll(list);
        }
    }
}
```

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

Теперь необходимо заменить класс `ListView` на `ListViewObserver`. Регистрация **Observer** будет происходить в классе контроллера приложения

{% code title="Main.java" %}
```java
    public void bindObserverToFacade(Observer o) {
        facade.addObserver(o);
    }
```
{% endcode %}

{% code title="Controller.java" %}
```java
public class Controller implements Initializable {

    private Main main;

    @FXML
    private ListViewObserver list;

    public void getMainController(Main main) {
        this.main = main;
        main.bindObserverToFacade(list);
    }
    
    ...
}
```
{% endcode %}

Нам осталось только реализовать функционал для кнопки «Удалить» и наше небольшое приложение теперь полностью готово.

Тестируем работу приложения.



Мы реализовали паттерн «Наблюдатель» вручную, но 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`~~.

Запустим приложение и убедимся, что его работа не изменилась, но мы избавились от необходимости создавать свой подкласс спискового элемента и вручную добавлять блок оповещения наблюдателей и вручную прописывать реакцию на оповещение об изменении данных.
