Реализация паттерна Observer в MVC (Model View Controller) для JavaFX
17-07-2018Время чтения ~ 5 мин.Java 10215
В продолжении предыдущей статьи «MVC (Model View Controller) в JavaFX», я решил-таки реализовать паттерн Наблюдатель (Observer), чтобы довести работу до логического завершения. За основу я взял код Java с Википедии. Этот пример из книги «Паттерны проектирования» Эрика и Элизабет Фримен, поэтому наверняка все его разбирали.
Пример я немного упростил, поскольку «погодная станция» нам не нужна, а также сделал так, чтобы уведомления могли быть разными.
В конце статьи вы найдете исходные коды для загрузки.
В отличие от предыдущего варианта программы, я добавил ещё одну метку, которая показывает текущую операцию. Она демонстрирует работу второго оповещения.
Общее описание паттерна «Наблюдатель»
Есть некий объект, который выполняет роль наблюдателя (Observable). Слушатель (Observer) — это те объекты, которые регистрируются у наблюдателя. Наблюдатель отправляет уведомления всем слушателям. Те его получают и дальше уже сами решают что с ним делать.
В проекте создаётся файл Observer.java:
package org.maxsite; /* * Это интерфейы для организации паттерна Наблюдатель * */ // слушатель interface Observer { void notification(String message); } // наблюдатель interface Observable { void registerObserver(Observer o); void notifyObservers(String message); }
Это обычные интерфейсы, где у наблюдателя будут функции регистрации и уведомления, а у слушателей — функция, которая будет срабатывать при поступлении уведомления.
Model
Модель будет Наблюдателем.
package org.maxsite; /* * здесь вычисления и внутреннее хранение данных * а также регистрация и уведомление слушателей * */ import java.util.LinkedList; import java.util.List; class Model implements Observable { private List<Observer> observers; // список слушателей private String op = "-"; // операция private String num1 = "1"; // первое число private String num2 = "2"; // второе число private String result = "1-2"; // результат // конструктор: создаем новый список для слушателей Model() { observers = new LinkedList<>(); } // регистрация слушателя @Override public void registerObserver(Observer o) { observers.add(o); } // уведомление слушателей @Override public void notifyObservers(String message) { for (Observer observer : observers) { observer.notification(message); } } // выставляет операцию void setOp(String s) { op = s; notifyObservers("op"); } // выставляет num1 void setNum1(String s) { num1 = s; } // выставляет num2 void setNum2(String s) { num2 = s; } // вычисляем результат void go() { result = num1 + op + num2; notifyObservers("go"); } // отдаем результат String getResult() { return result; } String getOp() { return op; } }
Слушатели хранятся в переменной observers, которая представляет собой список, который создается в конструкторе автоматически.
Уведомление рассылается с помощью notifyObservers() с параметром, где можно задать название сообщения. В этом варианте Модель может уведомить о нескольких событиях. В нашем случае идёт уведомление «go» при получении готового результата, и «op», когда меняется операция.
При этом Модель, опять же, ничего не знает о том, кто её использует.
Кто слушатель: View или Controller?
С ходу решить эту задачку у меня не получилось, поэтому пришлось повозиться. :-) Проблема в том, что для Контролёра в JavaFX мы не можем просто так вызвать конструктор. Это как-то связано с FXMLLoader, который сам его создает, а значит у нас нет возможности его переопределить, если она и есть (я не в курсе), то неясно насколько это окажется плохой идеей.
Можно было бы выделить класс Контролёра в main-файле и там уже зарегистрироваться в качестве слушателя, но мне такая идея не понравилась.
Поэтому в качестве слушателя может быть суперкласс View. При этом придется перенести в него создание Модели, иначе не получится зарегистрироваться. Соответственно Контролёр получает доступ к Модели автоматически, поскольку расширяет Представление.
View
Код View.java.
package org.maxsite; /* * это представление: то, что выводится на форму * */ import javafx.fxml.FXML; import javafx.scene.control.Label; class View implements Observer { // поля вывода @FXML private Label LabelResult; @FXML private Label LabelOp; // создаем модель здесь, поскольку это суперкласс для контролера Model model = new Model(); // конструктор: регистрация на сообщения модели View() { model.registerObserver(this); } // уведомления от модели @Override public void notification(String message) { // разные уведомления под разный вывод if (message.equals("go")) { this.displayLabel(model.getResult()); } else if (message.equals("op")) { this.displayOp(model.getOp()); } } // выводит текст в LabelResult (результат) private void displayLabel(String s){ LabelResult.setText("Результат: " + s); } // выводит текст в LabelOp (операция) private void displayOp(String s) { LabelOp.setText("Операция: " + s); } }
Регистрация в качестве слушателя происходит автоматически в конструкторе. При получении уведомления (функция notification()), выполняется либо вывод результата, либо вывод операции. Такое разделение позволяет без проблем добавить любой другой вывод на форму.
Controller
Теперь файл Controller.java.
package org.maxsite; /* * это контролер, он жестко завязан на форму * */ import javafx.fxml.FXML; import javafx.scene.control.TextField; public class Controller extends View { // форма: поля ввода @FXML private TextField tf1; @FXML private TextField tf2; // контролер знает модель из View // нажатите кнопки BtnPlus @FXML public void onActionBtnPlus() { model.setOp("+"); } // нажатите кнопки BtnMinus @FXML public void onActionBtnMinus() { model.setOp("-"); } // нажатите кнопки BtnGo - получение результата @FXML public void onActionBtnGo() { this.go(); } // функция для получения и вывода результата private void go() { // отдаем модели нужные данные model.setNum1(tf1.getText()); model.setNum2(tf2.getText()); // вычисляем результат model.go(); } }
Он стал ещё проще. За получение результата теперь отвечает Модель, которая автоматом доступна из View. То есть Контролёр только отправляет Модели данные при получении событий с кнопок.
Код достаточно простой, да ещё и с комментариями, поэтому добавить больше нечего.
Желающие могут скачать исходные файлы.