Шаблоны на практике

Соглашение относительно настройки

Джереми Миллер (Jeremy Miller)

Cодержание

Влияние развития языка
Скажите раз и только раз
Допустимые значения по умолчанию
Соглашение относительно настройки
Дальнейшие действия

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

Задумайтесь над этим на минуту. Если бы вы показывали свой код деловым представителям — предположим, что они действительно хотят прочитать код вместе с вами, — как много из этого кода будет важно для них? Этих деловых представителей, вероятно, будет интересовать только код, выражающий бизнес-функции системы. Этот код – «сущность» системы. С другой стороны, скорее всего, их мало будет интересовать такой код, как объявления типов, параметры настройки, блоки try/catch и универсальные ограничения. Этот код является инфраструктурой, или «формальностью», которую вы как разработчик должны тщательно разобрать, чтобы сдать код заказчику.

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

Влияние развития языка

Первый фактор, который я бы хотел рассмотреть, – это выбор языка программирования и использование языков программирования. Чтобы проиллюстрировать влияние языка программирования на объем формального кода, ненадолго отвлечемся на пыльные страницы истории.

В начале этого десятилетия я занимался созданием большой системы на Visual Basic 6.0. Каждый метод выглядел бы примерно так, как показано на рис. 1. Каждый бит этого кода был формальным.

Рис. 1. Соблюдение формальностей

Sub FileOperations()
    On Error Goto ErrorHandler

    Dim A as AType
    Dim B as AnotherType

    ' Put some code here

    Set A = Nothing
    Set B = Nothing
    ErrorHandler:
        Err.Raise Err.Number, _
            "FileOperations" & vbNewLine & Err.Source, _
            Err.Description, _
            Err.HelpFile, _
            Err.HelpContext
Exit Sub

Я использовал значительный объем стандартного текста для каждого метода, чтобы создать эквивалент трассировки стека с целью упрощения отладки. Мне также пришлось разыменовать переменные в методе (Set A = Nothing) для освобождения этих объектов. У меня был строгий стандарт проверки кода только для подтверждения правильности кода обработки ошибок и очистки объектов для каждого метода. Фактический код, сущность системы, находился где-то посередине этого формального кода.

Перенесемся в сегодняшний день к современным языкам программирования, таким как C# или Visual Basic .NET. Сегодня сборка мусора устранила большую часть явной очистки памяти, с которой приходилось сталкиваться в программировании Visual Basic 6.0. Трассировки стека в исключениях встроены в саму платформу Microsoft .NET Framework, поэтому более не требуется выполнять это самостоятельно. Если подумать обо всем механическом стандартном коде, который был устранен с переходом на платформу .NET, можно сказать, что языки, совместимые с .NET, более производительны и читаемы, чем Visual Basic 6.0, из-за уменьшения количества формального кода.

Платформа .NET стала большим шагом вперед, но развитие языков еще не закончено. Рассмотрим простое свойство в C#, реализованное классическим способом:

public class ClassWithProperty {
  // Higher Ceremony
  private string _name;
  public string Name {
    get { return _name; }
    set { _name = value; }
  }
}

Суть этого кода заключается в том, что класс ClassWithProperty имеет строковое свойство с именем Name. Перейдем к C# 3.0 и выполним то же самое при помощи автоматического свойства:

public class ClassWithProperty {
  // Lower Ceremony
  public string Name { get; set; }
}

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

Хотя, как правило, у разработчиков программного обеспечения отсутствует полный контроль над языками программирования. Хотя я определенно думаю, что необходимо использовать преимущества новинок в языках программирования или даже альтернативные языки, настал момент поговорить о идеях разработки, которые можно использовать сегодня при помощи популярных языков C# и Visual Basic.

Проверка допустимости для предметной области

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

  1. Логика проверки допустимости относится к бизнес-логике, и я предпочитаю, чтобы вся бизнес-логика находилась в классах бизнес-логики.
  2. Размещение логики проверки в модели или службах предметной области, отключенных от пользовательского интерфейса, может снизить дублирование на экранах и обеспечивает выполнение этой же логики проверки в службах, не относящихся к пользовательскому интерфейсу (например, веб-службах), предоставленных приложением (в очередной раз говоря это раз и только раз).
  3. Гораздо проще написать модульные приемочные тесты для логики проверки в модели, чем проверять эту же логику, реализованную как часть пользовательского интерфейса.

Скажите раз и только раз

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

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

Согласно принципу «Скажите раз и только раз» должен быть только один полномочный источник для любого факта или политики во всей системе. Рассмотрим пример создания веб-приложения с функциями создания, чтения, обновления и удаления (CRUD). В такой системе в основном выполняется изменение и сохранение полей данных. Эти поля должны быть изменены на экранах, проверены на сервере, сохранены в базе данных и, надеюсь, проверены и на клиенте для повышения удобства работы пользователей — но указать в коде, что «это поле необходимо и/или это поле не должно быть более 50 символов», нужно только один раз.

Здесь возможны различные подходы. Можно сделать схему базы данных определением основных данных и создать на основе схемы код промежуточного слоя и код презентации пользователя. Кроме того, можно определить поля данных в каком-либо внешнем хранилище метаданных, например файле XML, затем с помощью автоматического создания кода создать схему базы данных, какие-либо объекты промежуточного уровня и экраны пользовательского интерфейса. Лично я не любитель создания кода большого объема, поэтому моя команда выбрала другое направление.

Обычно сначала мы создаем классы модели предметной области. Мы решили, что за логику проверки допустимости должны отвечать классы объектов модели предметной области. Для простых правил проверки допустимости, таких как правила обязательных полей и максимальной длины строк, устанавливаются атрибуты проверки допустимости, такие как класс Address, показанный на рис. 2.

Рис. 2. Использование атрибутов проверки допустимости

public class Address : DomainEntity {
  [Required, MaximumStringLength(250)]
  public string Address1 { get; set; }

  [Required, MaximumStringLength(250)]
  public string City { get; set; }

  [Required]
  public string StateOrProvince { get; set; }

  [Required, MaximumStringLength(100)]
  public string Country { get; set; }

  [Required, MaximumStringLength(50)]
  public string PostalCode { get; set; }

  public string TimeZone { get; set; }
}

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

Однако теперь необходимо реплицировать правила для требуемых полей и максимальной длины строк в базу данных. Мы с командой использовали NHibernate в качестве механизма сохранения состояния. Одной из широко применимых возможностей NHibernate является создание кода языка описания данных (ЯОД) из сопоставлений NHibernate, который можно затем использовать для создания схемы базы данных и обеспечения синхронизации с моделью предметной области (эта стратегия, очевидно, наилучшим образом работает в новых проектах). Чтобы эта стратегия создания базы данных из модели предметной области была полезной, было необходимо добавление дополнительной информации к сопоставлениям NHibernate для отметки непустых полей и указания длины строк.

Мы использовали новый механизм Fluent NHibernate для определения сопоставлений объектов. В коде настройки для Fluent NHibernate мы установили автоматические преобразования в сопоставлениях путем указания Fluent NHibernate на способ обработки наличия атрибутов [Required] и [MaximumStringLength] в классах модели с помощью кода, показанного на рис. 3.

Рис. 3. Обработка атрибутов в NHibernate

public class MyPersistenceModel : PersistenceModel {
  public MyPersistenceModel() {
    // If a property is marked with the [Required]
    // attribute, make the corresponding column in
    // the database "NOT NULL"
    Conventions.ForAttribute<RequiredAttribute>((att, prop) => {
      if (prop.ParentIsRequired) {
        prop.SetAttribute("not-null", "true");
      }
    });

    // Uses the value from the [MaximumStringLength]
    // attribute on a property to set the length of 
    // a string column in the database
    Conventions.ForAttribute<MaximumStringLengthAttribute>((att, prop) => {
      prop.SetAttribute("length", att.Length.ToString());
    });
  }
}

Теперь эти соглашения будут применены ко всем соответствиям в проекте. Для класса я просто указал Fluent NHibernate сохраняемые свойства:

public class AddressMap : DomainMap<Address> {
  public AddressMap() {
    Map(a => a.Address1);
    Map(a => a.City);
    Map(a => a.TimeZone);
    Map(a => a.StateOrProvince);
    Map(a => a.Country);
    Map(a => a.PostalCode);
  }
}

Теперь, указав Fluent NHibernate атрибуты проверки, можно создать ЯОД для таблицы Address (см. рис. 4). Обратите внимание на то, что в коде SQL на рис. 4 длины строк соответствуют определению атрибутов [MaximumStringLength] класса Address. Аналогично, значения NULL / NOT NULL следуют из атрибутов [Required] класса Address.

Рис. 4. Создание кода ЯОД

CREATE TABLE [dbo].[Address](
  [id] [bigint] IDENTITY(1,1) NOT NULL,
  [StateOrProvince] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
  [Country] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
  [PostalCode] [nvarchar](50) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
  [TimeZone] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
  [Address1] [nvarchar](250) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
  [Address2] [nvarchar](100) COLLATE SQL_Latin1_General_CP1_CI_AS NULL,
  [City] [nvarchar](250) COLLATE SQL_Latin1_General_CP1_CI_AS NOT NULL,
PRIMARY KEY CLUSTERED 
(
  [id] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

Мы создали определенную инфраструктуру со структурой базы данных, производной от атрибутов проверки допустимости объектов модели предметной области, но пока отсутствует проверка и внешний вид на стороне клиента. Необходимо выполнить простую проверку допустимости ввода в обозревателе и определенным образом отметить необходимые элементы полей для повышения удобства работы пользователей.

Решение, найденное моей командой, заключалось в том, чтобы элементам ввода, отображенным в формате HTML, были известны атрибуты проверки допустимости. (Для контекста используется бета-версия 1 платформы MVC (модель – представление – контроллер) ASP.NET с механизмом веб-форм в качестве механизма представления.) На рис. 5 показан пример разметки для представления Address в нашей системе.

Рис. 5. Разметка представления Address

<div class="formlayout_body">
  <p><%= this.TextBoxFor(m => m.Address1).Width(300)%></p>
  <p><%= this.TextBoxFor(m => m.Address2).Width(300) %></p>
  <p>
    <%= this.TextBoxFor(m => m.City).Width(140) %>
    <%= this.DropDownListFor(m => 
        m.StateOrProvince).FillWith(m => m.StateOrProvinceList) %>
  </p>
  <p><%= this.TextBoxFor(m => m.PostalCode).Width(80) %></p>        
  <p><%= this.DropDownListFor(m => 
         m.Country).FillWith(m => m.CountryList) %></p>
  <p><%= this.DropDownListFor(m => 
         m.TimeZone).FillWith(m => m.DovetailTimeZoneList) %></p>
</div>

TextBoxFor и DropDownListFor – это небольшие вспомогательные классы HTML в общем базовом представлении, используемые для всех представлений в архитектуре MVC. Сигнатура TextBoxFor такова:

public static TextBoxExpression<TModel> TextBoxFor< TModel >(
  this IViewWithModel< TModel > viewPage, 
  Expression<Func< TModel, object>> expression)

  where TModel : class {
    return new TextBoxExpression< TModel >(
    viewPage.Model, expression);
  }

В этом коде важно отметить, что аргумент ввода – это Expression (если точнее, Expression<Func<TModel, object>>). При создании действительного HTML для текстового поля класс TextBoxExpression будет выполнять следующее.

  1. Преобразовывать выражение и находить точный объект PropertyInfo для свойства привязки.
  2. Запрашивать у PropertyInfo существование атрибутов проверки.
  3. Отображать HTML для текстового поля соответствующим образом.

Мы просто добавили необходимый класс для всех элементов HTML, привязанных к свойствам с атрибутом [Required]. Аналогичным образом при нахождении [MaximumStringAttribute] для свойства привязки мы устанавливали атрибут максимальной длины текстового поля HTML в соответствие с атрибутом и ограничивали ввод пользователя допустимой длиной. Полученный код HTML выглядит примерно так:

<p><label>Address:</label>
<input type="text" name="Address1" value="" 
       maxlength="250" style="width: 300px;" 
       class="required textinput" /></p>

Внешний вид необходимых полей контролируется путем простого редактирования внешнего вида необходимого класса CSS (мы установили голубой цвет необходимых полей на экране). Действительная проверка на стороне клиента выполнялась с помощью подключаемого модуля jQuery Validation, который, что достаточно удобно, просто ищет необходимый класс в элементах ввода. Максимальная длина текстовых элементов устанавливается с помощью атрибута maxlength элементов ввода.

Это ни в коем случае не было полной реализацией. Постепенное создание действительной реализации не было таким сложным. Сложной задачей был поиск способов устранения повторяющихся метаданных и создание кода в нескольких слоях. Я уверен, что многие разработчики не пойдут по пути моей команды (создание базы данных из объектной модели), но это не важно, поскольку моей действительной целью было просто поделиться некоторыми идеями об использовании принципа «Скажите раз и только раз» для ускорения разработки.

Допустимые значения по умолчанию

В предыдущем разделе я показал очень небольшой пример (через класс AddressMap) выражения объектно-реляционного сопоставления (ORM) при помощи Fluent NHibernate. Ниже приведен немного более сложный пример выражения ссылки из класса Site на объекты Address:

public SiteMap() {
  // Map the simple properties
  // The Site object has an Address property called PrimaryAddress
  // The code below sets up the mapping for the reference between
  // Site and the Address class
  References(s => s.PrimaryAddress).Cascade.All();
  References(s => s.BillToAddress).Cascade.All();
  References(s => s.ShipToAddress).Cascade.All();
}

При настройке средств ORM обычно необходимо выполнить следующие действия.

  1. Указать имя таблицы, которой сопоставлен класс Entity.
  2. Указать поле первичного ключа Entity и, как правило, также указать определенную стратегию для назначения значений первичного ключа. (Это автоматический номер/номер последовательности базы данных? Или назначает ли система значения первичного ключа? Или для первичного ключа используются идентификаторы GUID?)
  3. Сопоставить свойства объектов или поля столбцам таблицы.
  4. При создании отношения «к одному» между двумя объектами (например, сопоставления Site и Address) необходимо указать столбец внешнего ключа, который может использоваться средством ORM для объединения родительских и дочерних записей.

Все это обычно очень утомительная работа. Это формальность, необходимая для того, чтобы модель ORM сохраняла состояние объектов. К счастью, можно устранить часть утомительной работы путем встраивания определенных допустимых значений по умолчанию в код (обратите внимание на наличие заглавных букв).

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

public abstract class DomainMap<T> : ClassMap<T>, 
  IDomainMap where T : DomainEntity {

  protected DomainMap() {
    // For every DomainEntity class, use the Id property
    // as the Primary Key / Object Identifier
    // and use an Identity column in SQL Server,
    // or an Oracle Sequence
    UseIdentityForKey(x => x.Id, "id");
    WithTable(typeof(T).Name);
  }
}

Упрямое программное обеспечение

Можно заметить, что я описал переход на соглашения как «ограничивающий». Часть идей, лежащих в основе соглашения относительно настройки, заключаются в создании «упрямого программного обеспечения» для создания искусственных ограничений на проектирование.

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

Одно из мнений моей команды заключалось в том, что все классы модели предметной области полностью определены одним длинным свойством Id:

public virtual long Id { get; set; }

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

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

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

Кроме того, рассмотрите вариант использования средства статического анализа кода как части сборки непрерывной интеграции для автоматического применения соглашений проекта.

В этом коде устанавливается политика, в соответствии с которой подкласс DomainEntity будет определяться по свойству Id, назначенному стратегий идентификации. Предполагается, что имя таблицы совпадает с именем класса. Теперь можно всегда переопределять эти принципы для каждого класса, но это приходиться делать нечасто (класс User должен быть сопоставлен с таблицей с именем Users, просто чтобы избежать конфликтов с зарезервированным словом в SQL Server). Точно так же Fluent NHibernate подразумевает имя внешнего ключа на основе имени свойства, ссылающего на другой класс.

Конечно, экономится немного строк кода в классе сопоставления, но это упрощает читаемость различных частей сопоставления путем уменьшения общего избыточного кода в сопоставлении.

Соглашение относительно настройки

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

Соглашение относительно настройки – это подход и прием проектирования, заключающийся в применении параметров по умолчанию, которые могут быть выведены из структуры кода, вместо необходимости явного кода. Идея заключается в упрощении разработки передачи разработчику ответственности только за нестандартные части приложения и архитектуры.

Сейчас множество людей со рвением изучают платформу MVC ASP.NET и экспериментируют с различными способами ее использования. В модели MVC разработки веб-приложений существует несколько источников повторяющегося кода, которые могут быть отличной возможностью применения соглашений относительно настройки.

Ниже приведены пять этапов базового порядка выполнения одного запроса в модели MVC.

  1. Получение URL-адреса от клиента. Подсистема маршрутизации анализирует URL-адрес и определяет имя контроллера, обрабатывающего этот URL-адрес.
  2. Создание или поиск верного объекта контроллера на основе имени контроллера, определенного подсистемой маршрутизации.
  3. Вызов подходящего метода контроллера.
  4. Выбор подходящего представления, а также упаковка и передача данных модели, созданных из метода контроллера, в это представление.
  5. Отображение представления.

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

Первая задача – подключение входящего URL-адреса к веб-узлу с верным классом контроллера. Библиотека маршрутизации в платформе MVC может запросить URL-адрес и определить имя контроллера. После этого платформа MVC запросит зарегистрированный объект IControllerFactory для объекта контроллера в соответствии с именем контроллера, определенного из входящего URL-адреса.

Многие разработчики просто делегируют построение контроллера средству инверсии контроля (IOC). В случае моей команды мы использовали средство StructureMap с открытым кодом для разрешения экземпляров контроллера по имени:

public class StructureMapControllerFactory 
  : IControllerFactory {

  public IController CreateController(
    RequestContext requestContext, string controllerName) {

    // Requests the named Controller from the 
    // StructureMap container
    return ObjectFactory.GetNamedInstance<IController>(
      controllerName.ToLowerInvariant());
  }
}

Запрос контроллера выполняется довольно просто, но сначала необходимо зарегистрировать все классы контроллера по имени для контейнера IOC. Подождите! Не увеличивает ли это формальность архитектуры? Год или два назад мне пришлось окунуться в явную настройку IOC классов контроллера наподобие следующего:

public static class ExplicitRegistration {
  public static void BootstrapContainer() {
    ObjectFactory.Initialize(x => {
      x.ForRequestedType<IController>().AddInstances(y => {
        y.OfConcreteType<AddressController>().WithName("address");
        y.OfConcreteType<ContactController>().WithName("contact");

        // and so on for every possible type of Controller
      });
    });
  }
}

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

К счастью, в StructureMap присутствует прямая поддержка формальной регистрации, поэтому можно создать новый ControllerConvention с автоматической регистрацией всех конкретных типов IController:

public class ControllerConvention : TypeRules, ITypeScanner {
  public void Process(Type type, PluginGraph graph) {
    if (CanBeCast(typeof (IController), type)) {
      string name = type.Name.Replace("Controller", "").ToLower();
      graph.AddType(typeof(IController), type, name);
    }
  }
}

Затем необходим код запуска контейнера StructureMap с новым соглашением, как показано на рис. 6. После размещения нового ControllerConvention и загрузки части контейнера IOC все добавляемые к приложению новые классы контроллера автоматически добавляются к регистрации IOC без явной настройки разработчика. Отсутствуют какие-либо ошибки и сбои из-за того, что разработчик забыл добавить новые элементы настройки для новых экранов.

Рис. 6. Новые соглашения для StructureMap

/// <summary>
/// This code would be in the same assembly as 
/// the controller classes and would be executed
/// in the Application_Start() method of your
/// Web application
/// </summary>
public static class SampleBootstrapper {
  public static void BootstrapContainer() {
    ObjectFactory.Initialize(x => {
      // Directs StructureMap to perform auto registration
      // on all the Types in this assembly
      // with the ControllerConvention
      x.Scan(scanner => {
        scanner.TheCallingAssembly();
        scanner.With<ControllerConvention>();
        scanner.WithDefaultConventions();
      });
    });
  }
}

Дополнительно отмечу, что такая стратегия автоматической регистрации возможна во всех известных мне контейнерах IOC для .NET Framework, если контейнер IOC предоставляет интерфейс API программной регистрации.

Дальнейшие действия

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

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

Есть множество других тем, которые я не смог рассмотреть в этот раз, но надеюсь рассказать о них в следующей статье. Я определенно хочу рассмотреть влияние программирования, ориентированного на язык; таких альтернативных языков, как F#, IronRuby и IronPython, и использования внутреннего языка предметной области на процесс разработки программного обеспечения.

Вопросы и комментарии направляйте по адресу mmpatt@microsoft.com.

Джереми Миллер, обладатель звания Microsoft MVP в области C#, также является автором средства StructureMap с открытым кодом, предназначенного для введения зависимостей в .NET, и готовящегося к выпуску средства StoryTeller для тестирования перегрузки FIT в .NET. Посетите его блог The Shade Tree Developer, являющийся частью веб-узла CodeBetter.