Сегодня немного расскажу об архитектуре Composite Application Blocks (Web и UI) и о том, как ее использовать "во благо". Речь в основном пойдет о двух фундаментальных вещах: сервисах и шаблоне проектирования M-V-P (Model-View-Presenter).
В процессе работы над (любым) программным продуктом всегда хочется сделать так, чтобы отдельные части этого продукта, работая вместе "как часы", при этом как можно меньше зависели друг от друга. Для того, чтобы работа над одной из частей никак не затрагивала другие части или чтобы легко можно было вообще заменить одну из частей так, чтобы остальные части об этом ничего не узнали. Эта проблема называется "связанностью" и решение ее тем лучше, чем меньше различные части системы знают о том, как реализованы другие.
Слабой связанности в Composite Application Blocks можно добиваться посредством создания сервисов.
Кто такой сервис? Сервисом может быть любая независимая часть вашего приложения.
Сервис дает возможность приложению задавать вопросы типа "что" не заботясь об ответах на вопрос "как". Наше приложение просит сервис дать список пользователей - и получает его не заботясь о том, был ли список получен из БД или с Веб-сервиса. Приложение просит сервис информировать пользователя о прогрессе, не заботясь о том, будет ли пользователю показан ProgressBar или отобразится окошечко "Please Wait".
Приложение знает что надо делать. Сервис знает как надо делать. Приложение знает, что где-то есть, кто-то, кого можно пнуть, чтобы он сделал свою работу.
О том, как это реализуется.
Сервис обычно состоит из "двух частей" - интерфейса сервиса и его реализации. Интерфейс сервиса обычно размещается где-нибудь в доступном для всех (интересующихся) частей приложения месте (в Shared-библиотеке), а реализация - где придется там, где удобнее реализовывать этот интерфейс (например в каком-то модуле).
Например, мы определим интерфейсы ISourceControlService и IProgressService в общей библиотеке, чтобы все части приложения имели к ним доступ. После этого мы можем создать модуль SourceSaveVersionController, в котором создать класс SourceSaveService : ISourceControlService, которая будет работать работать с Microsoft SourceSave.
При загрузке и инициализации этого модуля происходит регистрация сервиса в системе:
Services.AddNew<SourceSaveService, ISourceControlService>();
С этого момента из любой части приложения мы можем обратиться к сервису, отвечающему за контроль версий, получить экземпляр, реализующий ISourceControlService и даже не задумываться о том, с какой именно системой контроля версий мы имеем дело:
ISourceControlService scControl = Services.Get<ISourceControlService>();
И теперь если мы заменим SourceSaveVersionController на, скажем, ClearCaseVersionController с соответствующей реализацией ISourceControlService, то для всего остального приложения эта смена пройдет более чем прозрачно. Потому, что остальное приложение знать не знает ни про какие модули и реализации, оно общается только с известным ему интерфейсом.
Для IProgressService можно поступить еще хитрее: реализовать этот интерфейс в главной форме приложения и зарегистрировать ее саму как сервис типа IProgressService. А можно сделать как-то иначе, придумать что-то другое, ведь уже стало понятно, что остальному коду совершенно безразлично то, кто предстанет перед ними под личиной IProgressService и как он будет выполнять свою работу :)
Сама работа с сервисами в Composite Application Blocks тоже очень удобна.
Например, для регистрации сервиса в CAB можно использовать такую конструкцию:
[Microsoft.Practices.CompositeUI.Service(typeof(IComponentsProvider))]
public class AccessComponentsProvider : IComponentsProvider
{.....}
В этом случае при загрузке модуля экземпляр сервиса будет автоматически создан и зарегистрирован.
Там, где нам необходимо общаться с сервисами, и в CAB и в Composite Web AB можно сделать так:
[ServiceDependency]
public IAuthorizationService AuthService
{
get { return _authService; }
set { _authService = value; }
}
ObjectBuilder сам найдет для вас подходящий сервис IAuthorizationService (если, конечно, он был зарегистрирован) и вызовет присвоит ссылку на него свойству AuthService. В случае, если нужный сервис зарегистрирован не был, вы получите null. Однако, в конструктор аттрибута ServiceDependency можно передать булевый параметр для того, чтобы ObjectBuilder возбуждал исключение если сервис найти не удается.
В заключение хотел бы еще раз подчеркнуть: всю функциональность, которую можно сделать сервисом, настоятельно рекомендую делать именно сервисом. Это позволяет добиться большой гибкости за счет отделения интерфейса от реализации и легко поддерживается.
В этом случае не нужно думать о том, экземпляр какого класса создать или какой фабрикой классов воспользоваться для того, чтобы сделать что-то. Не нужно и создавать десятки экземпляров в различных местах для того, чтобы воспользоваться определенным функционалом - всегда есть т��т, кто готов выполнить ваш запрос.
Наконец, не нужно знать каким синглтоном воспользоваться, как и вообще плодить синглтонов, которые не дают такой гибкости при работе с ними.
Используйте сервисы - и да будет вам счастье :)