Вступление
Я разрабатываю приложения JavaFX уже около 5 лет и использовал для этого различные шаблоны архитектуры.
Сначала я начал с MVC, затем с MVVM, и, наконец, мы использовали пассивный просмотр MVP в моем текущем проекте.
Способ применения шаблона MVC хорошо описан в документации JavaFX, MVVM хорошо описан в документации фреймворка mvvmFX, но для пассивного представления MVP я потратил много времени на чтение разных статей об этом, но я не нашел способ, который на 100% подходит для меня.
Кроме того, я не нашел удовлетворительной статьи о том, как координировать различные представления, построенные с помощью шаблона MVP.
Поэтому я оценил различные способы создания сложного пользовательского интерфейса JavaFX с представлениями, следующими шаблону пассивного представления MVP, и способы их координации.
Мое внимание было сосредоточено на:
- чтобы избежать двунаправленных зависимостей между представлением и презентером
- найдите наилучший способ управления специфичными для домена свойствами представлений
- родительские компоненты присоединяют дочерние компоненты
- контроллеры рабочих процессов получают доступ к компонентам.
- как и где общаться с серверной частью
Поэтому я пришел к следующему выводу, который мне нравится.
Структура компонента пользовательского интерфейса
Простой компонент пользовательского интерфейса с несколькими привязками свойств представления
Компонентный Модуль
Весь компонент связан в контексте, который управляет зависимостями. Я использовал guice, поэтому есть частный модуль guice, который управляет внутренними зависимостями.
Более глобальные модули, использующие этот модуль, должны обеспечивать предоставление всех зависимостей, необходимых докладчику.
Ведущий
Основная цель докладчика – предоставить свойства, зависящие от домена, к которым представление может привязываться, и предоставить методы, которым представление может напрямую делегировать.
Это ответственно какая информация отображается, а не как.
Докладчик не должен содержать бизнес-логику, только логику о адаптации между представлением и бизнес-логикой.
Ведущий не знает представления.
public class Presenter { private final StringProperty someString = new SimpleStringProperty(); void doSomething() { // not implemented yet } StringProperty someStringProperty() { return someString; } }
Смотреть
Представление отвечает за то, как отображается информация.
Вид – это скромный объект. Он делает только три вещи:
- предоставьте пакет – частный заводской метод
- свяжите свойства элементов управления со свойствами докладчиков и делегируйте действия элементов управления докладчику в методе инициализации
- предоставьте свой корневой узел общедоступными методами
public class View { @FXML private StackPane root; @FXML private Label label; @FXML private Button button; private final Presenter presenter; private View(Presenter presenter) { this.presenter = presenter; } static View createInstance(Presenter presenter) { View view = new View(presenter); FXMLLoader.load("....", view); return view; } @FXML void initialize(){ label.textProperty().bind(presenter.someStringProperty()); button.setOnAction(event -\> presenter.doSomething()); } public Node getRootNode() { return root; } }
Более сложный компонент пользовательского интерфейса с несколькими привязками свойств представления
Чтобы сохранить презентер в чистоте, мы передаем свойства представления на аутсорсинг в своего рода модель представления.
Viewmodel – это только вспомогательный класс для представления и докладчика, поэтому его уровень доступа должен быть только частным для пакета.
Вложенные компоненты пользовательского интерфейса
Вложите компонент пользовательского интерфейса в качестве дочернего компонента пользовательского интерфейса (вложенность 1:1)
Если вы хотите вложить дочернее представление, вам необходимо установить его модуль в родительский модуль.
Таким образом, вы можете просто ввести открытое представление дочернего компонента в заводской метод родительского представления.
При необходимости его презентатор может напрямую ввести в родителей презентатора.
Вложите компонент как своего рода элемент управления (вложенность 1:n)
В основном я думаю, что пользовательский элемент управления не следует рассматривать как представление и, следовательно, не следует реализовывать с помощью шаблона MVP.
Было бы решение, чтобы справиться с этим делом таким образом:
- ведущий такого “компонента управления” должен быть доступен через само представление, а не через модуль
- поставщик модулей представления должен быть введен в родительское представление
- родительское представление должно заботиться о создании экземпляров дочерних представлений/элементов управления и распространять их на своего собственного докладчика, который соединяет логику представления
Но, как вы видите, шаблон MVP не подходит для этого типичного случая использования для введения пользовательского элемента управления, поэтому я не буду углубляться в этот случай.
Координация компонентов пользовательского интерфейса
У пользователя всегда есть причина для использования интерфейса системы. Таким образом, приложения основаны на вариантах использования.
Реализация этих вариантов использования проецируется в некоторых абстрактных объектах, которые я называю контроллером рабочего процесса.
Для этого существуют разные концепции и названия, но в целом они создают контекст варианта использования или рабочего процесса, и цель состоит в том, чтобы сохранить контроль над рабочим процессом на очень абстрактном уровне.
На уровне пользовательского интерфейса их обязанностью является координация и организация компонентов пользовательского интерфейса, создание контекста, в котором они взаимодействуют друг с другом и как они взаимодействуют с серверной частью.
В зависимости от сложности пользовательского интерфейса и вариантов использования, во многих случаях они не будут управлять компонентами пользовательского интерфейса напрямую, а будут абстрагироваться некоторыми вспомогательными компонентами с различными обязанностями.
Чтобы контроллер рабочего процесса был отделен от докладчика, доступ к докладчикам должен быть абстрагирован функциональным интерфейсом, который реализован докладчиком и используется контроллером рабочего процесса.
Обратный вызов от докладчика к контроллеру рабочего процесса может быть реализован либо путем введения функции обратного вызова докладчику, прикрепления списков к его свойствам, либо позволяя докладчику запускать события, которые перехватываются контроллером рабочего процесса.
Другая идея состояла бы в том, чтобы управлять связью между контроллером рабочего процесса и презентатором с помощью модели конкретной задачи, которая содержит свойства и функции обратного вызова.
Но, на мой взгляд, интерфейс более ориентирован на объект, и гораздо сложнее поддерживать модель чистой и независимой от рабочего процесса и материалов докладчика, чем интерфейс.
Таким образом, ведущий сверху мог выглядеть так:
public class Presenter implements WorkflowTask{ private final StringProperty someString = new SimpleStringProperty(); private Consumer\onDoSomething; @Override public void showSomeString(String string) { someString.set(string); } @Override public void setOnDoSomething(Consumer\ onDoSomething) { this.onDoSomething = onDoSomething; } void doSomething() { onDoSomething.accept(someString.get()); } StringProperty someStringProperty() { return someString; } }
Взаимодействие с серверной частью
Контроллеры рабочих процессов обрабатывают взаимодействие с серверной частью.
По соображениям тестируемости я предпочитаю использовать класс обслуживания, который внутренне делегирует услугу fx и возвращает завершенное будущее своим клиентам.
Если вас интересуют Angular и AWS, прочитайте мою статью о как перенести angular spa в облако
.
Сообщение Архитектура приложения JavaFX появилось впервые на маймаркт .
Оригинал: “https://dev.to/_maimart_/architecture-of-a-javafx-application-2geb”