Зачастую при проектировании UI форм интерфейса возникает задача скомпоновать стандартные элементы управления в некоторый более высокоуровневый объект с индивидуальным поведением в целях инкапсуляции и/или повторного применения.
В данном посте я опишу возможность повторного использования презентера для управления различными элементами UI.
Сама идея повторного использования презентера противоречит догме Билла Кратохвила о том, что «презентер не предназначен для повторного использования», я не собираюсь опровергать авторитетного эксперта (без иронии), а изложу мое видение.
Итак, еще раз, кто такой презентер:
Presenter – (англ.) представитель, а в жаргоне сотрудников TV эфира (TV news presenter) - ведущий (программы). Последнее, по моему мнению, как нельзя лучше отображает его назначение. Я пошёл дальше и провел аналогию презентера с продюсером эстрадной звезды, детально расписав цели, роли, ответственности, но нашел в себе силы вовремя остановиться J
И все же, в концепции шаблона MVP-VM, presenter является представителем не только интерфейса пользователя, но и отвечает за то, каким функционалом этот интерфейс будет обладать. Функционал представления здесь ключевая фраза, именно эту характеристику я и буду повторно использовать.
Задача
В прошлом посте была достигнута цель разделения ответственности (SRP) между объектами уровня представления и объектами бизнес-логики, код разнесён по корректным классам Model, View, Presenter и ViewModel. В качестве примера была построена форма управления заказами конкретного менеджера. Теперь мне необходимо тот же список управления заказами перенести на форму управления старшего менеджера. Старший менеджер контролирует заказы своих подчинённых сотрудников и по необходимости может управлять их списками.
Следовательно, список заказов на форме старшего менеджера и список заказов формы простого менеджера должен иметь один и тоже функционал (выводить таблицу и предоставлять возможность управления заказами).
При такой задаче GridView выносится на общий usercontrol OrderListControl, теперь необходимо задать поведение элементу управления в концепции шаблона MVP-VM.
В предыдущем посте презентер управлял жизненным циклом представления (View) таким образом, что на единицу жизненного цикла презентера могло приходиться сколько угодно жизненных циклов представления (открытий/закрытий формы). Выделяя таблицу в отдельный usercontrol и добавляя его на форму, более нет необходимости управлять его жизненным циклом, эта задача переходит в зону ответственности формы клиента.
Далее необходимо создать презентер который можно будет повторно использовать для управления любым объектом реализующим общий протокол IOrderListControlView, в частности получившийся элемент управления OrderListControl.
Возможность повторного использования кода достигается инверсией зависимостей (DIP), если презентер будет явно ссылаться на представление, то он не может быть повторно использован, отсюда правило:
Презентер не зависит от конкретной реализации пользовательского интерфейса (View)
Из схемы видно, что презентер зависит от абстракции и управляет реализацией интерфейса IOrderListControlView, следовательно, любой объект, реализующий IOrderListControlView может быть передан для управления OrderListControlPresenter (представителю списка заказов). В код презентера также переносится вся логика реализации вариантов использования в рамках user story «управление заказами» (создание, редактирование и пр.), а также реализация команд (IComand) управления списком.
Как было сказано выше, если для OrderListControlPresenter в данном контексте исключена возможность управления жизненным циклом представления, а он должен каким-то образом получить реализацию View, то настраивается соответствующий метод инициализации презентера, и ответственность за инициирование данной процедуры также переносится в зону ответственности объекта клиента.
Комментарий к приведенному коду:
OrderListControlPresenter содержит метод инициализации Initialize, принимающий экземпляр элемента управления пользовательским интерфейсом (UserControl) реализующий интерфейс IOrderListControlView. Отныне, для презентера реализация IOrderListControlView является представлением (View) и ни чем иным, и что за ним кроется в частности: форма, элемент управления UI, Console, монитор и пр. не имеет никакого значения. Получив экземпляр представления, презентер первым делом настраивает его на отображение данных ViewModel.
Схема взаимодействия клиентских презентеров с OrderListControlPresenter.
EmployeeListFormPresenter (представитель формы списка менеджеров) и OrderLisrFormPresenter (представитель формы списка заказов) реализуют и управляют жизненными циклами своих представлений IEmployeeListFormView и IOrderListFormView соответственно. Перенося на каждую из форм элемент управления списком заказов (OrderListControl) и декларируя в интерфейсах View поддержку реализации IOrdelListControlView, презентеры этих форм посредством отслеживания ассоциаций имеют доступ к экземпляру представления списка заказов, которым они инициализируют OrderListControlPresenter, и могут передавать ему управление:
Комментарий к приведенному коду:
Создание и отображение формы списка менеджеров начинается с метода EmployeeListFormPresenter.ShowView(), в котором: происходит создание экземпляра формы c добавлением элемента OrderListControl, инициализация OrderListControlPresenter и отображение созданной формы.
Далее, после привязки списка менеджеров к форме EmployeeListForm возникает событие изменения текущего менеджера, форма предает управление своему презентеру вызвав метод EmployeeListFormPresenter.ChangeCurrentEmployee(employee : object). Презентер формы проверяет корректность атрибутов, выполняет собственные операции по отображению информации о выбранном менеджере и передает управление презентеру списка заказов (orderListControlPresenter.ShowEmployeeOrders(currentEmployee : Employee)) который, проводит все необходимые операции для вывода заказов менеджера и предоставления всего функционала управления этим списком.
Управление списком заказов
OrderListControl кроме контекстного меню не содержит других элементов управления. Вместо конкретного интерфейса OrderListControlPresenter предоставляет клиентам перечень реализаций ICommand, посредством которых клиенты могут управлять списком заказов, привязывая команды к собственным элементам управления.
Заключение
Посредством инверсии зависимостей стало возможным повторное использование презентера для управления несколькими объектами UI. За счет инкапсуляции ответственностей в дочерние объекты, структура клиентских презентеров приблизилась к модульной и стала прозрачнее. Аргументы в пользу "реюза" кода можно перечислять очень долго, но на этом я остановлюсь.
Исходный код демо-решения можно скачать здесь.
Используемые источники:
Комментариев нет:
Отправить комментарий