# Лекция 14

## Тема: Принцип Separation of Concerns. Контроллер и представление. Паттерн MVС. Паттерн Observer. Разработка приложения с соблюдением архитектуры MVC.

## Отделение графического интерфейса от бизнес-логики приложения. Язык FXML.

До этого момента мы создавали графический интерфейс напрямую в коде приложения, явно создавая объекты графического интерфейса, меняли их свойства, назначали слушатели событий и так далее.

Такой подход обладает рядом недостатков:

* такой программный код превращается в череду созданий объектов и вызовов методов для их настройки, что затрудняет чтение кода и приводит к тому, что он выглядит не лучшим образом;
* такой код тяжело отлаживать и исправлять ошибки при реализации графического интерфейса. Необходимо постоянно компилировать код и смотреть полученный результат в работающем приложении;
* графический интерфейс, как правило, проектируется отдельным специалистом в области UI/UX, который может не знать языка, на котором этот интерфейс проектируется (в нашем случае, это Java). Кроме того, это приводит к тому, что java-программист, кроме программирования логики приложения, должен проектировать GUI, что приводит к дополнительным материальным затратам;
* в случае необходимости изменения графического интерфейса (добавления нового функционала, изменения внешнего вида, анимации, эффектов), необходимо «перелопачивать» код и вносить туда изменения, что приводит к многочисленным ошибкам и тормозит процесс разработки программного обеспечения.

Основополагающим принципом разработки программного обеспечения является принцип separation of concerns

Исходя из этого, следует запомнить первое правило, которому должен следовать любой программист при разработке даже самого простого приложения: **внешний вид (пользовательский интерфейс) приложения всегда должен быть максимально отделен от бизнес-логики работы приложения**. Этот принцип еще кратко называют принципом **отделения представления от содержания** (эти принципы называются «**Separation of concerns**» или «**Separation of presentation and content**»).

{% hint style="info" %}
В общем случае, принцип separation of concerns формулируется следующим образом - программа должна быть разделена на функциональные блоки, как можно меньше перекрывающие функции друг друга.
{% endhint %}

Повторим еще раз, что пользовательский интерфейс и внутренняя логика работы программы должны быть максимально независимы друг от друга. В идеале, вашей программе должно быть все равно – в каком виде она представлена пользователю – в виде GUI, консольного приложения, в окне браузера или еще как-то. В идеале, при изменении GUI, остальным классы программы остаются нетронутыми и изменения в них вносить не надо (в реальности, конечно, изменения вноситься будут, но они должны быть минимальны). Это помогает сделать программу более гибкой, а также помогает добиться разделения труда: программист пишет код, а дизайнер (верстальщик, специалист по UI/UX) занимается проектированием графического интерфейса.

Подавляющее большинство современных графических библиотек и фреймворков обладают тем или иным механизмом отделения представления от содержания.

Давайте разберемся, как это реализовано в JavaFX. Рассмотрим простой пример. Создадим пустое JavaFX-приложение

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

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

В примере определен объект `Stage`, объект `Scene` и корневой элемент графа сцены. Очевидным следующим шагом было бы создание новых объектов графического интерфейса, установка свойств объектов и формирование графа сцены.

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

Выполним несколько шагов, чтобы настроить работу IntelliJ IDEA для использования языка FXML:

1. скачаем и установим приложение **SceneBuilder** по ссылке [http://gluonhq.com/products/scene-builder/](http://gluonhq.com/products/scene-builder/);
2. зайдем в настройки IntelliJ IDEA (Ctrl+Alt+S или File -> Settings) и в пункте JavaFX укажем путь к exe-файлу установленного **SceneBuilder**;
3. перезапустим IntelliJ IDEA.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MUNlBaAb16CorJgqJzl%2F-MUNrKDyTaRVSc9hbtlK%2Fimage.png?alt=media\&token=01b3395f-f9cc-4217-bd99-ff7f4601ae91)

Откроем наш проект с JavaFX-приложением и создадим в проекте новый FXML файл `sample.fxml`.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MUNlBaAb16CorJgqJzl%2F-MUNrMpovdpnHIDkppDd%2Fimage.png?alt=media\&token=0f1cef46-89fc-4019-bb89-5f574e4d62af)

Давайте разберемся, зачем мы создали этот файл и зачем нам нужен язык FXML. В JavaFX пользовательский интерфейс можно описать с помощью специального языка разметки, который называется FXML. Язык FXML является подмножеством языка XML и немного напоминает HTML.

Таким образом, вместо того, чтобы создавать объекты пользовательского интерфейса на языке Java в исходном коде программы, мы описываем граф сцены в виде fxml-файла, который выглядит определенным образом. Благодаря тому, что XML позволяет описать любые данные, мы можем очень точно описать объекты и их свойства, а древовидная структура XML идеально описывает граф сцены, который тоже имеет структуру дерева.

Откроем файл `sample.fxml` и посмотрим его содержимое

{% code title="sample.fxml" %}
```markup
<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane xmlns="http://javafx.com/javafx"
            xmlns:fx="http://javafx.com/fxml"
            fx:controller="sample.Sample"
            prefHeight="400.0" prefWidth="600.0">

</AnchorPane>
```
{% endcode %}

Как мы видим, текст файла напоминает формат XML, в котором присутствуют некоторые элементы языка Java (в частности, подключение библиотек с помощью `import`).

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

{% code title="sample.fxml" %}
```markup
<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<HBox xmlns="http://javafx.com/javafx"
      xmlns:fx="http://javafx.com/fxml"
      prefHeight="400.0" prefWidth="600.0">
    <children>
        <Button text="Кнопка"/>
        <VBox prefHeight="200.0" prefWidth="100.0">
            <children>
                <Label text="Текст"/>
            </children>
        </VBox>
    </children>
</HBox>
```
{% endcode %}

Итак, давайте попробуем разобраться в этом коде:

* строка 1 – в ней содержится так называемая XML-декларация. Она описывает версию XML и кодировку;
* строки 3 – 6 отдаленно напоминают команды импорта в java, которые заключены между символами `<?` и `?>`. Такая конструкция называется инструкцией обработки (Processing Instruction, PI), фактически, это инструкция тому приложению, которое будет «читать» XML-файл. Фактически, это означает инструкцию – подключить те или иные библиотеки;
* строки 8-10 определяет корневой элемент графа сцены – элемент `HBox`. В качестве атрибутов xmlns указаны пространства имен для тэгов FXML. Все остальные элементы размещаются внутри HBox, что в точности соответствует структуре дерева;
* в строке 11 указан элемента `<children>`. Потомки того или иного элемента содержатся внутри элемента `<children>`, что напоминает метод `getChildren()`, который мы используем для добавления потомков элемента;
* свойства элементов (высота, ширина, текст надписи и так далее) описываются в виде атрибутов тех или иных элементов.

Таким образом, данное описание довольно понятно и легко читается человеком, который хоть сколько-нибудь знаком с форматом XML и с элементами JavaFX.

Граф сцены, описанный в файле `sample.fxml` с помощью традиционного способа создания объектов Java в исходном коде выглядит следующим образом:

```java
@Override
public void start(Stage primaryStage) throws Exception {

    HBox hBox = new HBox();
    hBox.setPrefHeight(400);
    hBox.setPrefWidth(600);
    
    Button button = new Button("Кнопка");
    Label label = new Label("Текст");
    
    VBox vBox = new VBox();
    vBox.setPrefHeight(200);
    vBox.setPrefWidth(100);
    
    vBox.getChildren().add(label);
    hBox.getChildren().addAll(button,vBox);
    
    Scene scene = new Scene(hBox, 300, 200);
    primaryStage.setTitle("JavaFX-приложение");
    primaryStage.setScene(scene);
    primaryStage.show();
}
```

**Визуальные редакторы UI**

Использование специального языка описания FXML дает нам возможность использовать специальные визуальные редакторы, которые позволяют нам проектировать граф сцены в визуальном режиме.

В качестве визуального редактора мы будем использовать редактор **SceneBuilder**, который мы установили и подключили выше.

При работе с fmxl-файлами вы можете легко переключаться между текстовым и визуальным представлением файла, редактировать код, который сгенерировал визуальный редактор и видеть результат без необходимости каждый раз компилировать и запускать приложение.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MUNlBaAb16CorJgqJzl%2F-MUNs5oItS5T2qM-n82b%2Fimage.png?alt=media\&token=f7c4e3e8-0a1d-4c84-9e82-4438a5f84274)

Использование редактора очень сильно облегчает и ускоряет разработку графических приложений. Кроме того, при работе с fxml очень удобно подключать css-стили и даже вызывать скрипты на различных языках программирования, например, например, Groovy, Clojure и даже JavaScript (данный вопрос в рамках этого курса не рассматривается).

Используя визуальный редактор, создадим граф сцены, который визуально будет выглядеть следующим образом

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MUNlBaAb16CorJgqJzl%2F-MUNsBpY25CuvHzGnhGw%2Fimage.png?alt=media\&token=d44fdbce-b6d1-42ff-9ab9-612b40ecb406)



В формате XML этот код будет выглядеть следующим образом:

{% code title="sample.fxml" %}
```markup
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>

<!--        xmlns="http://javafx.com/javafx"-->
<!--        xmlns:fx="http://javafx.com/fxml"-->

<GridPane fx:controller="sample.SampleController" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8.0.1" xmlns:fx="http://javafx.com/fxml/1">
  <columnConstraints>
    <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
    <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
  </columnConstraints>
  <rowConstraints>
    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
    <RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
  </rowConstraints>
   <children>
      <Button maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" mnemonicParsing="false" text="Кнопка 1" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS">
         <font>
            <Font size="22.0" />
         </font>
         <GridPane.margin>
            <Insets />
         </GridPane.margin>
      </Button>
      <Button maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Кнопка 2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.vgrow="ALWAYS">
         <font>
            <Font size="22.0" />
         </font>
      </Button>
      <Button maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Кнопка 3" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.vgrow="ALWAYS">
         <font>
            <Font size="22.0" />
         </font>
      </Button>
      <Button maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Кнопка 4" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" GridPane.vgrow="ALWAYS">
         <font>
            <Font size="22.0" />
         </font>
      </Button>
      <Button maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Кнопка 5" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2" GridPane.vgrow="ALWAYS">
         <font>
            <Font size="22.0" />
         </font>
      </Button>
      <Button maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" mnemonicParsing="false" text="Кнопка 6" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2" GridPane.vgrow="ALWAYS">
         <font>
            <Font size="22.0" />
         </font>
      </Button>
   </children>
</GridPane>

```
{% endcode %}

У нас есть готовый граф сцены, но что дальше? Как известно, в Java всё является объектами некоторых классов, в том числе и элементы графического интерфейса.

До этого мы просто создавали в исходном коде объекты нужных нам классов, после чего вызывали их методы для формирования нужного нам графа сцены. Но у нас только текстовое описание графа сцены в формате FXML, как его преобразовать в набор объектов, вызвать нужные методы этих объектов и сформировать граф сцены?

**Генерация объектов графа сцены. Загрузчик** **FXML**

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

Фреймворк JavaFX использует распространенный подход, который состоит в **генерации объектов**. Его суть состоит в следующем: в JavaFX существует специальный класс `FXMLLoader` (загрузчик FXML), который содержит статический метод `load()`. Этот метод реализует следующий функционал:

1. метод считывает fxml-файл, URL которого вы должны указать;
2. метод «парсит» fxml-файл (разбивает файл на отдельные элементы с атрибутами);
3. используя результаты парсинга и механизм рефлексии (механизм рефлексии в данном курсе не рассматривается), метод создает объекты, устанавливает их свойства и помещает один объект в состав других объектов и таким образом формирует нужный нам граф сцены;
4. в качестве возвращаемого значения, метод `load()` возвращает ссылку на корневую вершину (root node), которая прямо или опосредованно содержит в себе весь сгенерированный граф сцены.

Вернемся к нашему исходному коду. Используя полеченные знания о работе класса FXML, выполним следующие шаги:

1. вызовем статический метод `FXMLLoader.load()`, на вход которого передадим URL ресурса, в качестве которого выступает наш fxml-файл (что такое ресурс и как нам получить URL ресурса подробнее читайте здесь [https://goo.gl/FJfvR9](https://goo.gl/FJfvR9));
2. ссылку на корневой элемент графа сцены передадим в созданный объект сцены.

Полученный код выглядит следующим образом.

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

    @Override
    public void start(Stage primaryStage) throws Exception {

        Parent root =
                FXMLLoader.load(getClass().getResource("sample.fxml"));

        Scene scene = new Scene(root, 300, 200);

        primaryStage.setTitle("JavaFX-приложение");
        primaryStage.setScene(scene);
        primaryStage.show();
    }


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

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

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MUTPL5xacBEZBbDg1X-%2F-MUTYG0jOdQYVQz1dAnc%2Fimage.png?alt=media\&token=dcd27031-ba6b-4023-97d2-4260ed11c71f)

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

Но сразу же возникает следующая проблема, **каким образом получить ссылки на эти сгенерированные объекты?** Если мы хотим указать обработчик событий для элементов графического интерфейса или динамически изменить свойства некоторых элементов – нам нужна ссылка на объекты элементов графического интерфейса.

Когда мы создавали объекты самостоятельно в исходном коде классов, мы хранили ссылку на них и могли к ним обратиться.  Но в нашем случае, объекты были сгенерированы в недрах класса `FXMLLoader`, упакованы один в другой и всё, что у нас есть – ссылка на корневой элемент графа сцены.

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

### **Контроллер графа цены. Получения ссылок на объекты графа сцены.**

Получение ссылки на сгенерированный элемент графического интерфейса – это одна из проблем, которая возникает, когда объекты графического интерфейса создаются не вручную. В различных графических библиотеках и в разных языках программирования эта проблема решается по-разному. Давайте рассмотрим, как эта проблема решается в JavaFX.

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

Класс-контроллер это обычный java-класс, который должен реализовывать интерфейс `javafx.fxml.Initializable`, который определяет всего один метод `initialize()`. Создадим класс `SampleController` и пока оставим его пустым.

{% code title="SampleController.java" %}
```java
public class SampleController implements Initializable {
    @Override
    public void initialize(URL location, ResourceBundle resources) {
    }
}
```
{% endcode %}

Дальнейший шаг – в fxml-файле необходимо указать, что для данного графа сцены будет использоваться этот класс контроллера. Эта информация будет считана `FXMLLoader` при парсинге fxml-файла.

Откроем файл `sample.fxml` и в корневом узле графа сцены укажем атрибут `fx:controller = ”ua.opu.SampleController”`.

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MUqtdQ63avUm5JD3Gnb%2F-MUqwYQtXx16wqTvIlOY%2Fimage.png?alt=media\&token=f3b25262-e440-4468-ab2a-386e3a71153b)

Таким образом, мы заявляем, что для данного fxml-файла будет использоваться указанный класс контроллера. Этот атрибут можно использовать только в корневом узле fxml-файла.

Чтобы отличить один объект элемента UI от других, необходимо присвоить элементам UI различные идентификаторы, используя которые мы сможем обратиться к тому или иному элементу. Для того чтобы установить идентификатор для элемента, необходимо указать атрибут `fx:id` в коде либо в свойствах в визуальном редакторе.

Идем дальше. Теперь я хочу каким-то образом в классе контроллера получить ссылку на первые две кнопки и как-то ними дальше манипулировать. Как мне это сделать? Чтобы получить доступ к каким-то элементам, мне нужно этим элементам присвоить определенный id. Вы можете сделать это либо в визуальном редакторе (выделите нужный элемент, зайдите во вкладку **Code:** и установите значение в поле `fx:id`).

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MUTYQ1Se_mhYsYOZ4mt%2F-MUTYw_VJ9IQmN07LIEq%2Fimage.png?alt=media\&token=0d17976d-3d79-4e73-9294-d71babcb4156)

Для данного примера присвоим идентификаторы для двух кнопок: `button1` и `button2`.

Нам необходимо получить ссылки на объекты этих кнопок. Для этого зайдем в класс контроллера и создадим два поля типа `javafx.scene.control.Button`, **с названиями, которые точно совпадают с идентификаторами этих кнопок в** **fxml-файле.**  После чего, укажем перед каждым полем аннотацию `@FXML`. В итоге, класс контроллера будет иметь следующий вид

{% code title="SampleController.java" %}
```java
public class SampleController implements Initializable {
    
    @FXML
    private Button button1;
    
    @FXML
    private Button button2;
    
    @Override
    public void initialize(URL location, ResourceBundle resources) {
    }
}
```
{% endcode %}

{% hint style="info" %}
Еще раз обратите внимание, что `fx:id` и имя переменной должно совпадать точно и перед каждым объявлением должно быть указано `@FXML`.
{% endhint %}

Давайте разберемся, что происходит при указании контроллера и полей с аннотацией `@FXML`:

* класс `FXMLLoader` парсит fxml-файл;
* загрузчик «считывает» атрибут `fx:controller` и создает объект этого класса;
* загрузчик парсит аннотации `@FXML` и внедряет в эти поля ссылки на созданные объекты (используется механизм рефлексии);
* загрузчик вызывает метод `initialize()` (в этом методе мы прописываем все наши манипуляции с нужными элементами GUI).

Таким образом, если мы правильно указали `fx:id` и не ошиблись с классами и названиями полей, то в момент вызова метода `initialize()`, наши поля `button1` и `button2` будут содержать ссылки на наши две кнопки.

Добавим обработчик нажатия на кнопку 1

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

    @FXML
    private Button button1;

    @FXML
    private Button button2;

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

        button1.setOnAction(event -> {
            new Alert(Alert.AlertType.CONFIRMATION,"Вы нажали кнопку 1").showAndWait();
        });
    }
}
```
{% endcode %}

Всё прошло успешно, мы имеем ссылки на нужные нам элементы, и мы можем делать с ними что захотим.

Для второй кнопки реализуем слушатель иначе. В классе-контроллере создадим метод `handleButton2()` и тоже пометим его аннотацией `@FXML`.

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

    @FXML
    private Button button1;

    @FXML
    private Button button2;

    @FXML
    protected void handleButton2(ActionEvent event) {
        new Alert(Alert.AlertType.CONFIRMATION,"Вы нажали кнопку 2").showAndWait();
    }

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

        button1.setOnAction(event -> {
            new Alert(Alert.AlertType.CONFIRMATION,"Вы нажали кнопку 1").showAndWait();
        });
    }
}
```
{% endcode %}

То есть, мы видим, что мы можем помечать этой аннотацией не только поля, но и методы. Далее, зайдем в fxml-файл, в режим визуального редактора, выделите вторую кнопку, зайдите во вкладку **Code:** и в поле **On** **Action** из выпадающего списка выберите нужный метод

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MUTYQ1Se_mhYsYOZ4mt%2F-MUTZGWNi3EWtvxVHwva%2Fimage.png?alt=media\&token=0e6b266d-4cfe-444c-955b-3d1092ca5705)

В текстовом виде это выглядит следующим образом

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MUTYQ1Se_mhYsYOZ4mt%2F-MUTZIm1NeCHoOHRuFoW%2Fimage.png?alt=media\&token=9a084043-c50b-4032-aca2-2b503ee21e9c)

Компилируем приложение и смотрим результат

![](https://1377473627-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LnhxGhlv6e_zwoZYywB%2F-MUTYQ1Se_mhYsYOZ4mt%2F-MUTZKBS0GvP_tWVWkwC%2Fimage.png?alt=media\&token=b8a7442e-d0ec-4cb0-9d64-32ef152cb7d5)

Как вы уже, наверное, поняли, фактически это означает следующее «если для кнопки2 произошло событие Action – вызови метод `handleButton2()`. Таким образом, если у вас много слушателей, то удобнее просто закодировать методы и расставить нужные методы для нужных событий в нужных элементах, и не париться лишний раз с полями.

## Паттерн 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`~~.

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