Commands
Определенный Фасад инициализирует Контроллер, передавая Командам связанные с ними Оповещения необходимые для старта приложения.
Для каждой связки (Mapping), Контроллер регистрируется как Наблюдатель над данным Оповещением. Когда происходит Оповещение, Контроллер создает экземпляр соответствующей Команды. И, наконец, Контроллер вызывает у созданной команды метод execute(), передавая в него данное Оповещение.
Команды единоразовы; они создаются по требованию (приходит Оповещение), и после их исполнения (вызов метода execute) они должны удаляться. В связи с чем, важно не создавать экземпляры и не хранить ссылки на Команды в объектах, которые будет использоваться в течении долго срока в вашем приложении.
Использование Макро- и Простых команд
Команды, как и все классы фреймворка PureMVC, реализовывают интерфейс, а именно интерфейс ICommand. PureMVC содержит две реализацию ICommand интерфейса для Макро-команды которую вы можете с легкостью расширять.
Класс Command (Макрокоманда) позволяет выполнять несколько подкоманд последовательно, каждой из которых будет после создания передан экземпляр текущего Оповещения.
Класс Command из конструктора вызывает свой метод initializeSubCommands. Вы должны переопределить его в своем классе макрокоманды, для вызова метода addSubCommand для каждой команды, которую вы ходите добавить в макрокоманду. Вы можете добавлять любые Команды или Макрокоманды.
Для того, чтобы кастомная команда была "Простой", достаточно переопределить метод execute, который принимает в качестве параметра экземпляр INotification. Вписываете вашу функциональность в метод execute и все готово к работе.
Слабое связывание Команд с Медиаторами и Прокси
Команды выполняются Контроллером в результате отправки Оповещения. Команды не должны быть созданы и выполнены никем кроме Контроллера.
Чтобы общаться и взаимодействовать с другими частями системы, Команды могут:
- Регистрировать, удалять или проверять существуют ли Медиаторы, Прокси и Команды.
- Посылать Оповещения, для получения ответной реакции от других команд или Медиаторов.
- Получать экземпляры Прокси и Медиаторов и управлять ими напрямую.
Команды позволяют нам легко переключать элементы Представления между различными состояниями (через Медиаторы), или передавать данные к различным частям этих элементов.
Они могут быть использованы для осуществления транзакционного взаимодействия с Моделью, которую охватывают несколько Прокси, требовать отправки Оповещения по окончанию транзакции, или обрабатывать непредвиденные ситуации (exceptions) и принимать соответствующие действия.
Взаимодействие сложных действий и бизнес-логики
местах вашего приложения, где вы могли бы разместить код (Команд, Медиаторов и Прокси) неизбежно и периодически будет возникать вопрос:
Какой код и где писать? Что конкретно должна делать Команда?
Первое разделение логики в вашем приложении коснется бизнес-логики и логики предметной области.
В командах находится бизнес-логика нашего приложения; ожидается, что техническая реализация сценариев использования нашего приложения будет выполняться на уровне модели предметной области (Domain Model). Это подразумевает координацию Модели и состояниями Представления.
Модель поддерживает свою целостность, используя Прокси, которые размещены в логике предметной области (Domain Logic), открывая API для работы с объектами данных. Они инкапсулируют весь доступ к моделям данных, находящимся на клиенте или на сервере, для того чтобы остальная часть приложения которая работает с данными, могли иметь к ним синхронный или асинхронный доступ.
Команды могут быть использованы для управления рядом действий в системе, которые должны происходить в определенном порядке, с возможностью того что результат предыдущего действия может быть использован для последующего.
Медиаторы и Прокси должны предоставлять крупномодульные интерфейсы Командам (и наоборот), которые скрывают реализацию объектов данных и компонентов представления, которыми они управляют.
Заметьте, что когда мы говорим про компонент представления, мы имеем в виду кнопку или виджет, с которым непосредственно взаимодействует пользователь. Когда мы говорим об объекте данных, то подразумеваем произвольные структуры данных, а также удаленные сервисы, которые могут быть использованы для хранения или получения данных.
Команды взаимодействуют с Медиаторами и Прокси, но должны быть изолированы от граничных реализаций. Вот пример того как Команда используется для подготовки приложения к работе:
import type { NotificationInterface } from '../interfaces/NotificationInterface';
export default (Module) => {
const {
STARTUP, STARTUP_COMPLETE,
Command,
PrepareControllerCommand,
PrepareModelCommand,
PrepareViewCommand,
initialize, partOf, meta, method, nameBy
} = Module.NS;
@initialize
@partOf(Module)
class StartupCommand extends Command {
@nameBy static __filename = __filename;
@meta static object = {};
@method initializeSubCommands(): void {
this.addSubCommand(PrepareControllerCommand);
this.addSubCommand(PrepareModelCommand);
this.addSubCommand(PrepareViewCommand);
}
@method execute<T = ?any>(note: NotificationInterface<T>): void {
super.execute(note);
this.facade.removeCommand(STARTUP);
this.send(STARTUP_COMPLETE);
}
}
}
Это макрокоманда, которая содержит три подкоманды, которые при вызове макрокоманды исполняются в порядке очереди (FIFO).
Это создает 'очередь' верхнего уровня из действий, которые должны быть выполнены на старте приложения. Но что конкретно должны мы сделать, и в каком порядке?
Прежде чем пользователь увидит или сможет взаимодействовать с приложением и его данными, Модель должна быть надлежащим образом подготовлена. Как только это сделано, Представление может быть подготовлено для отображения данных Модели и позволить пользователю взаимодействовать с ними.
Следовательно, процесс старта обычно состоит из трех обширных групп операций — подготовка Контроллер, Модели, и последующая подготовка Представления.
import type { NotificationInterface } from '../interfaces/NotificationInterface';
export default (Module) => {
const {
MSG_FROM_CONSOLE, CLEAR_CONSOLE,
Command,
initialize, partOf, meta, method, nameBy
} = Module.NS;
@initialize
@partOf(Module)
class PrepareControllerCommand extends Command {
@nameBy static __filename = __filename;
@meta static object = {};
@method execute<T = ?any>(note: NotificationInterface<T>): void {
this.facade.addCommand(MSG_FROM_CONSOLE, 'SimpleCommand');
this.facade.addCommand(CLEAR_CONSOLE, 'SimpleScript');
}
}
}
Подготовка Контроллера, обычно, является простым созданием и регистрацией всех Команд, в которых приложение будет нуждаться при запуске.
Пример команды PrepareControllerCommand, это Простая команда, которая подготавливает Контроллер к дальнейшей работе. Это первая из подкоманд макрокоманды, и поэтому она будет выполнена первой.
Через конкретный Фасад, она регистрирует те Команды, которые система будет использовать при запуске.
import type { NotificationInterface } from '../interfaces/NotificationInterface';
import type { ApplicationInterface } from '../interfaces/ApplicationInterface';
export default (Module) => {
const {
APPLICATION_PROXY, SIMPLE_PROXY,
Command,
initialize, partOf, meta, method, nameBy
} = Module.NS;
@initialize
@partOf(Module)
class PrepareModelCommand extends Command {
@nameBy static __filename = __filename;
@meta static object = {};
@method execute<T = ?any>(note: NotificationInterface<T>): void {
const app: ApplicationInterface = note.getBody();
this.facade.addProxy(APPLICATION_PROXY, 'ApplicationProxy', app.initialState);
this.facade.addAdapter('SimpleAdapter');
this.facade.addProxy(SIMPLE_PROXY, 'SimpleProxy');
}
}
}
Подготовка Модели, обычно, является простым созданием и регистрацией всех Прокси, в которых приложение будет нуждаться при запуске.
Пример команды PrepareModelCommand, это Простая команда, которая подготавливает Модель к дальнейшей работе.
Через конкретный Фасад, она создает и регистрирует те Прокси, которые система будет использовать при запуске. Заметьте, что Команда не делает никаких манипуляций или инициализаций с данными Модели. Прокси отвечает за любое получение данных, создание или инициализацию необходимых Объектов Данных для использования в системе.
import type { NotificationInterface } from '../interfaces/NotificationInterface';
import type { ApplicationInterface } from '../interfaces/ApplicationInterface';
export default (Module) => {
const {
APPLICATION_MEDIATOR, SHELL, LOGGER_MODULE, SIGNALS_GENERATOR,
Command,
initialize, partOf, meta, method, nameBy
} = Module.NS;
@initialize
@partOf(Module)
class PrepareViewCommand extends Command {
@nameBy static __filename = __filename;
@meta static object = {};
@method execute<T = ?any>(note: NotificationInterface<T>): void {
console.log('PrepareViewCommand execute()');
const app: ApplicationInterface = note.getBody();
this.facade.addMediator(LOGGER_MODULE, 'LoggerModuleMediator');
this.facade.addMediator(SHELL, 'ShellJunctionMediator');
this.facade.addMediator(APPLICATION_MEDIATOR, 'ApplicationMediator', app);
this.facade.activateMediator(APPLICATION_MEDIATOR);
this.facade.activateMediator(LOGGER_MODULE);
this.facade.activateMediator(SHELL);
if (!app.isLightweight) {
this.facade.addMediator(SIGNALS_GENERATOR, 'SignalsMediator');
this.facade.activateMediator(SIGNALS_GENERATOR);
this.facade.addMediator('SimpleMediator');
this.facade.activateMediator('SimpleMediator');
}
}
}
}
Это простая команда которая подготавливает Представление для работы. Это последняя из подкоманд макрокоманды, следовательно, будет выполнена последней.
Отметьте, что создается и регистрируется только Медиатор ApplicationMediator, который обслуживает компонент представления приложения.
В дальнейшем, тело Оповещения передается в конструктор медиатора. Это ссылка на приложение, переданная самим приложением вместе с Оповещением, когда первоначальное Оповещение STARTUP было послано. (Согласно предыдущему примеру приложения MyApp.)
Приложение это в некоторой степени специальный компонент Представления, в котором реализуются и находятся в качестве потомков все другие компоненты Представления, которые инициализируются при запуске приложения.
Для взаимосвязи с остальной частью системы, компоненты Представления должны обладать Медиаторами. И создание этих Медиаторов требует ссылку на компоненты Представления, с которыми они будут работать, а это на данном этапе известно только приложению.
Медиатор приложения - это единственный класс, которому допускается знать все про реализацию Представления приложения, так что создание оставшихся Медиаторов будет размещена внутри его конструктора.
Используя эти три команды, мы обеспечили последовательную инициализацию Модели и Представления. При этом Команды не должны знать много о Модели или о Представлении.
При изменении Модели или реализации Представления, Прокси и Медиаторы должны быть изменены соответствующим образом. Бизнес-логика внутри Команд не должна зависеть от изменений, происходящих в областях применения.
Модель должна формировать «логику предметной области», поддерживая целостность данных внутри Прокси. Команды выполняют «транзакционную» или «бизнес» логику в Модели, формируя координацию транзакций мульти-Прокси или обрабатывая и сообщая об исключительных ситуациях.