Основы создания модульных приложений

    • Silverlight
    • WPF
    • CompositeWPF
  • modified:
  • reading: 5 minutes

В дополнение к вебкасту Prism: Composite Application Guidance for WPF and Silverlight хотелось бы сделать еще несколько умозаключений. “Призма” на данный момент очень распространенный подход к разработке программных продуктов. За рубежом если вы найдете какую-нибудь работу на WPF или Silverlight первый вопрос, который вам зададут “А знаете ли вы призму?”. Чтобы узнать ее можете посмотреть первые 20 минут скринкаста, где я описываю основные моменты данного подхода. Узнать призму – это безусловно хорошо, но еще лучше знать зачем она нужна. Постараюсь привести пару простых примеров модульных приложений.

Модульные приложения

Главная идея Призмы – помощь в разработке модульных приложения. Под модулями понимается разделение именно по функциональности, а не на уровне архитектуры приложения. На самом деле, в хорошем приложении, каково бы оно не было, часто есть неявное разбиение на модули, вроде разброс на разные папки aspx страниц, либо xaml окон одной предметной области. Да и руководитель проекта раскидывает обычно задачи разработчикам в зависимости от функциональности. Например, если мы пишем CRM: то задачи можно разбить так – “ты занимайся информацией о клиентах, а ты занимайся расписанием (задачи, встречи и т.п.)”. Как раз это и можно разбить на настоящие модули, а именно на отдельные библиотеки. Когда разбивается на модули представление, то что-то нужно делать и со слоями, отвечающими за модель данных и доступа к данным (тут как связь между клиентом и сервером, если, к примеру, используем Silverlight, так и доступ к БД). Само собой, на каком то уровне дробление на модули прекратится либо из-за невозможности разбить, либо потому что нет необходимости. В любом случае, вряд ли вы будете дробить вашу базу данных на модули.

Что же даст нам четкое разбиение на модули? В случае Silverlight приложения – если наше приложение имеет разграничения прав доступа к функционалу, то в зависимости от прав пользователя подгружать разные модули: будет небольшой выигрыш в трафике. В этом же и в любом другом случае, если обеспечена достаточная независимость модулей друг от друга – возможность продавать разные компоновки нашего программного продукта. А как обеспечивать эту самую независимость? С этой задачей помогает иногда справиться Dependency Injection (DI). Почему иногда? Небольшие костыли все же нужно будет иногда подставлять.

Инъекция представлений

Давайте рассмотрим на примере как можно разобраться с зависимостями. Опять CRM. Пускай у нас есть модуль для работы с компаниями, и есть отдельный модуль с контактными лицами, который может быть в подставке с компаниями, а может и не быть. В случае, когда модуль “контактные лица” куплен – существует отдельный справочник с подробной информацией о каждом контактном лице, с историей взаимодействия с этим контактным лицом, в общем, не суть, просто много дополнительного функционала завязанного на контактном лице. Как же нам сделать эти модули действительно независимыми друг от друга? Ведь тут прослеживается тесная связь между компаниями и контактными лицами, а именно у компании есть список контактных лиц. Давайте зарисуем наше представление, есть форма детальной информации о компании и есть список контактных лиц этой компании, располагающейся на этой же странице. В случае купленного модуля контактных персон в списке должно быть больше информации, так же каждая строка должна иметь возможность открыть (перейти) на страницу детальной информации о персоне, а если модуль не куплен, тогда должно быть более простое представление с редактированием прямо в гриде.

sample form

Напрашивается такой выход, у нас есть Region “Contact Persons List” и нам необходима инъекция в этот регион определенной формы в зависимости от условия. Решаем задачу так: в модуле компаний создаем интерфейс IContactPersonsView – представление списка контактов, у которого есть, предположим, метод инициализации с передачей идентификатора компании. Помимо модуля контактных персон у нас будет еще модуль заглушка, который можем назвать BaseContactPersonsModule, предоставляющий нам только базовую функциональность работы с контактными лицами (только список). В обоих этих модулях будет реализация интерфейса представления IContactPersonsView. Теперь нам остается найти механизм, который сможет подставить в регион “Contact Persons List” необходимое представление. Тут нам поможет DI контейнер. Модули контактных персон при загрузке будут регистрировать соответствия своего типа к интерфейсу IContactPersonsView. А модуль компаний при отображении этого окна будет находить зарегистрированный тип к интерфейсу IContactPersonsView, создавать новый объект этого типа и делать инъекцию в необходимый регион. Еще нужно сделать зависимость, чтобы модуль компании загружался именно после модуля персон, чтобы он уже мог получить соответствие, какое представление зарегистрировано на его регион.

Работа с событиями

Есть другой пример, теперь у нас представление выглядит так:

sample form

Есть два контрола, один список компаний Companies List и второй зависимый от первого Details List, который опять же может отображать либо список контактных персон, либо список истории взаимодействия выбранной в верхнем контроле компании. В случае если это не модульное приложение, либо эти два контрола находятся на одной форме всегда – мы можем их связать при помощи самой формы – установить соответствие между выбранным элементом и списком детальной информации. В случае модульного приложения у нас нет понимания, на какой форме какое наше представление (контрол) будет располагаться, а так же нет должного представления, какой именно контрол будет там располагаться. Здесь нам бы помог некий механизм публикации событий. То есть контрол Companies List при выборе определенной компании публикует через данный механизм информацию об этом событии (каким-то образом его идентифицируя), а контрол Details List (либо даже это может быть набор контролов) просто подписывается через данный механизм на события этого типа. Самый простой способ реализация данного подхода – сделать статический event в каком-нибудь классе, к которому есть доступ как из Companies List, так и из Details List. В CompositeWPF для этого предоставлен Event Aggregator.

Итог

Как вы увидели из мною приведенных примеров CompositeWPF решает не так много задач, точнее он только помогает с одной задачей – модульностью. Притом модульность можно достаточно просто решить и без CompositeWPF. Создавать модульные приложения – очевидно, что хорошо и полезно. Чем меньше зависимостей между компонентами, тем проще будет создавать новый функционал. В начале затраты скорее всего по времени будут больше чем реализация в лоб, но в будущем выигрыш будет при должном подходе. И как видно из этих же примеров – остаются другие вопросы – что делать с доступом к данным, с объектами бизнес логики, стоит ли их разбивать на модули? Тут однозначного ответа нет, все зависит от задачи и Призма не дает ответы на данные вопросы.

See Also