Главная » C# » Реализация стандартной архитектуры в Visual C# (Sharp)

0

Среди разработчиков программного обеспечения в последнее время широко обсуждается тема "соглашения превыше конфигурации". Это обсуждение  пряло особенно активную форму с выходом инфраструктуры Ruby on Rails (http://www.rubyonrails.org/). Инфраструктура Ruby on Rails (или просто Rails) являет собой инструмент, позволяющий быстро создавать Web-страницы, предоавляющие полезную функциональность. Большинство разработчиков постоянно ищет способы для более быстрого выполнения задач, и Rails является одним из тих способов.

Многие приписывают успех Rails его применению концепции "традиция превыше конфигурации". Другие говорят, что это благодаря языку Ruby. А третьи потому, что Ruby является профессиональным продуктом.

Вернемся к проблеме динамической загрузки кода или, вернее, динамическому иолнению кода. Как вы думаете, сколько программист должен знать и сколько он должен предполагать? Рассмотрим следующий код:

interface IDefinition   { } void Dolt(IDefinition def) {

// Выполняется какая-либо операция с def.

}

В коде мы видим интерфейс IDefinition и метод Doit о с параметром типа IDefinition. Это создает контракт, по которому при вызове Doit о ему необходо передать экземпляр типа IDefinition.

Будет ли  правильным предположить, что динамическая загрузка типа может удоетворить требования контракта для Doit о? Можем ли мы предполагать, что тип вообще способен ПОДДерЖИВаТ Ь  IDefinition?

Согласно концепции "соглашения превыше конфигурации" производится попытка создать скопления автономной функциональности, которая может сама обеспечить свои требования. Для данной автономной функциональности может потребоваться применение конфигурационного файла или других сборок. Но что ей не требуется, так это директива с исходным кодом, который прямым образом указывает ей, что делать и когда.

Рассмотрим ситуации в каком-либо офисе или в любой другой рабочей среде. Роводитель, вместо того чтобы указывать подчиненным, что делать на каждом шу, дает только задание, а в выполнении данного задания предполагает имеющиеся у каждого сотрудника знания, дополненные  знаниями его коллег. Полагаться  на свои знания и хорошо, и плохо. Хорошо потому, что ограничивается число испоителей, плохо потому, что может быть избран неправильный подход.

Следующий код является примером соглашения:

interface ICommand { void Run();

}

ConfigurationLoader.Instance.Load(); IDefinition definition =

ConfigurationLoader.Instance.Instantiate<ICommand>("Impll");

definition.Run();

Код исполняет какой-то другой код посредством метода Run о и не имеет ни возвраемого значения, ни выходных параметров. Вызывающий код надеется, что испоение вызываемого кода будет благополучным. В этом и заключается суть концеии "соглашения превыше конфигурации" — вызывающий код надеется, что все будет хорошо. Напоминаю, что это аналогия сотрудничества равных единиц.

По большому счету, соглашение работает довольно хорошо, потому что система на основе соглашений легче поддается расширению и сопровождению, т. к. содержит

меньше рабочих компонентов. Для любой сложной  системы  применим  принцип: чем меньше рабочих компонентов, тем лучше. Но обратной стороной этого являея необходимость, чтобы администратор знал, какие имеются рабочие компоненты.

Люди имеют склонность к тому, чтобы все было конфигурируемым, и не оставлт никаких решений компьютерной программе. Но будет ли конфигурация рабать, зависит от того, как она написана. В архитектуре соглашений вызываемая функциональность будет принимать решения по вопросам, которые она считает нужными.

Примером конфигурации может служить следующий код: [ConfigurationProperty("typename",  IsRequired = true)] public string TypeName {

get {

return (string)base[_propTypeName];

}

}

Код должен быть вам знакомым, т. к. он похож на конфигурационный код, рамотренный ранее. Но в этом коде слишком много ограничений, и для него требтся слишком много рабочих компонентов. Его можно упростить в следующий код (который не скомпилируется, т. к. мы не можем изменить кодовую базу библиоти .NET):

[ConfigurationProperty()] public string TypeName {

get {

return (string)base[_propTypeName];

}

}

Разница между этими двумя фрагментами кода состоит в отсутствующих парамеах в атрибутах .NET во втором фрагменте. Параметры не являются обязательни, Т. К. ОНИ уже объявлены членом данных propTypeName, и мы можем использать идентификатор свойства в качестве этой дополнительной информации. Таким образом, идентификатор свойства TypeName можно было бы использовать в качесе атрибута XML.

Некоторые могут доказывать, что наличием перекрестной ссылки между идентифатором свойства и идентификатором конфигурации мы создаем жестко закодиранную зависимость. Это законный аргумент. Но является ли предположение, котое делается в коде, разумным предположением? Будет ли чрезмерным сказать, что идентификатор свойства является именем атрибута XML? Нет, не будет, и разрабоики Ruby on Rails сказали то же самое, когда создавали свою архитектуру.

Теперь рассмотрим возможность указывать, является ли конфигурационный  элент необходимым. Делая это, мы можем избежать исключения далее в исполнении.’

Но думайте в более обширном контексте. Атрибут .NET isRequired обрабатываея при запуске программы. При компиляции корректность конфигурационного файла не проверяется, поэтому единственное, что делает isRequired, — это создт исключение на более раннем этапе. Возможно, вы хотите избежать ошибок врени исполнения, которые могли бы вызвать фатальный сбой программы. Я не даю, что это будет большой выгодой, но уверен, что многие не согласятся со мной; поэтому решение, использовать ли свойство isRequired, принимается каждым состоятельно.

ПРИМЕЧАНИЕ

Целью архитектуры по  соглашению  является  разрешить  максимальное  числ о  допений,  не  жертвуя  более  высокой  целью  приложения.  Создание  хорошей  архитектуры по соглашению — задача не из легких, т. к. для этого требуется полное понимани е пребности разработчиков и пользователей приложения. Мое практическое правило — сначала решить проблему, а потом думать, что должно быть сконфигурировано, а что оставлено  по  соглашению.

 Динамическая загрузка базового класса или типов интерфейса

В этой главе рассматриваются две категории динамически загружаемого кода. Первой категорией является тип, реализующий интерфейс  (implementation и IDefinition). Второй категорией  является класс, производный от другого  класса (LoaderSection и configurationSection). Каждый подход имеет свои преимущтва и недостатки, но для обоих применимо следующее правило.

Что использовать, интерфейс или класс, зависит от того, сколько ответственности вы хотите делегировать реализации. В примере обработки конфигурации инфртруктура .NET конкретно указывала, что единственной ответственностью динически загружаемого класса является указание, какие идентификаторы необходо  обработать.  Как  извлекать  значение  идентификаторов  и  преобразовывать  их в типы .NET, является ответственностью базового класса.

Когда при создании экземпляра типа используется интерфейс, мы делегируем весь контроль динамически созданному экземпляру типа. Вызывающий код прямо говит: "Вот твой контракт. Как ты его выполнишь — твое дело. Только обеспечь, чтобы контракт был выполнен должным образом". Делегируя ответственность, мы, в определенном смысле, напрашиваемся на неприятности, т. к. разработчики могут реализовать контракт неправильно.

Другие факторы, такие как производительность и ресурсы, также играют роль, но я думаю, что они не так важны, как понимание единственного правила делегировия ответственности.

Источник: Гросс  К. С# 2008:  Пер. с англ. — СПб.:  БХВ-Петербург, 2009. — 576 е.:  ил. — (Самоучитель)

По теме:

  • Комментарии