Главная страница   /   12.4. Конфигурирование сложных API (Внедрение зависимостей в .NET

Внедрение зависимостей в .NET

Внедрение зависимостей в .NET

Марк Симан

12.4. Конфигурирование сложных API

До настоящего момента мы рассматривали то, как можно конфигурировать компоненты, использующие Constructor Injection. Одним из главных преимуществ Constructor Injection является то, что DI-контейнеры, например, Spring.NET, могут с легкостью понимать, как компоновать и создавать все классы диаграммы зависимостей.

Все становится менее понятным, когда API не столь хорошо функционируют. В этом разделе вы увидите, как работать с простейшими аргументами конструктора, статическими фабриками и Property Injection. Все это требует особого внимания. Давайте начнем с рассмотрения классов, которые принимают в качестве параметров простейшие типы, например, строки и целые числа.

Конфигурирование простейших зависимостей

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

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

Рассмотрим в качестве примера приведенный ниже конструктор:

public ChiliConCarne(Spiciness spiciness)

В этом примере Spiciness имеет перечисляемый тип:

public enum Spiciness
{
	Mild = 0,
	Medium,
	Hot
}

Предупреждение

Согласно эмпирическому правилу перечисления являются code smell'ами и их нужно преобразовывать в полиморфные классы (имеющие разное состояние). Тем не менее, для этого примера они вполне нам подходят.

Необходимо явным образом передать значение параметра конструктора spiciness в виде составляющей конфигурации объекта ChiliConCarne:

<object id="Course" type="ChiliConCarne">
	<constructor-arg value="Hot" />
</object>

Строка 2: Передача значения

Spring.NET снабжен конвертерами нескольких типов, которые конвертируют текстовые элементы в экземпляры необходимых типов. Один из встроенных конвертеров преобразует текст в элемент перечисляемого типа, что позволяет нам сделать текст "Hot" значением элемента constructor-arg. Spring.NET смотрит на тип параметра конструктора класса ChiliConCarne, определяет, что это параметр перечисляемого типа и использует конвертер соответствующего типа для преобразования текста "Hot" в значение Spiciness.Hot.

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

Конфигурирование статических фабрик

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

Рассмотрим приведенный ниже пример конструктора открытого класса JunkFood:

internal JunkFood(string name)

Даже если класс JunkFood является открытым, конструктор расположен внутри него. Очевидно, экземпляры JunkFood должны создаваться с помощью статического класса JunkFoodFactory:

public static class JunkFoodFactory
{
	public static IMeal Create(string name)
	{
		return new JunkFood(name);
	}
}

С точки зрения Spring.NET, это проблемное API, поскольку в нем отсутствуют точно выраженные и заданные соглашения касательно статических фабрик. Тут требуется помощь – и мы можем предоставить ее при конфигурировании объекта:

<object id="Meal"
				type="JunkFoodFactory"
				factory-method="Create">
	<constructor-arg value="chicken meal" />
</object>

Как и всегда, конфигурация объекта выражается в элементе object, но вместо задания типа самого объекта, вы задаете тип фабрики. Помимо этого вы должны определить, что именем factory-method будет Create. Заметьте, что хотя метод Create по существу и не является конструктором, вы все равно используете элемент constructor-arg для того, чтобы задать значение аргумента name метода Create.

Хотя атрибут type задается как тип фабрики, а не тип результата, Spring.NET достаточно умен, чтобы понять, что выходной тип метода Create – это JunkFood. Это означает, что вы можете не использовать атрибут id и создать неименованный объект, как вы это делали в разделе 12.3.1 "Выбор из составных кандидатов", и вы все равно сможете разрешить объект JunkFood с помощью метода GetObjectsOfType.

Последним рассматриваемым нами отклонением от паттерна Constructor Injection является Property Injection.

Property Injection – это менее определенная форма механизма внедрения зависимостей, поскольку компилятор не принудает нас задавать значение свойства, доступного для записи. Все-таки, Spring.NET понимает Property Injection и работает с ним интуитивным образом. Если мы разрешим автоматическое интегрирование, то Property Injection будет работать, но, помимо этого, он будет работать и посредством прямого интегрирования.

Рассмотрим класс CaesarSalad:

public class CaesarSalad : ICourse
{
	public IIngredient Extra { get; set; }
}

По всеобщему заблуждению в состав салата "Цезарь" входит курица, но это не правда. По существу "Цезарь" является салатом, но, поскольку с курицей он вкуснее, то ее часто предлагают использовать в нем в качестве дополнительного ингредиента. Класс CaesarSalad моделирует такую возможность посредством доступного для записи свойства под названием Extra.

Если вы конфигурируете только класс CaesarSalad, явно не обращаясь к свойству Extra, то этому свойству не будет присвоено значение. Вы все равно можете разрешать экземпляр, но свойство Extra будет иметь значение по умолчанию, которое ему присвоил конструктор (если только это имеет место).

Явное интегрирование свойств

Существует несколько способов, с помощью которых можно сконфигурировать CaesarSalad таким образом, чтобы свойство Extra заполнялось соответствующим образом. Один из таких способов – явным образом интегрировать свойство с именованным объектом:

<object id="Course" type="CaesarSalad">
	<property name="Extra" ref="Chicken" />
</object>
<object id="Chicken" type="Chicken" />

Элемент property указывает на то, что именем свойства является "Extra", и что в качестве его значения необходимо использовать объект Chicken. Вместо того чтобы использовать ссылку на именованный объект, вы можете использовать вложенный объект:

<object id="Course" type="CaesarSalad">
	<property name="Extra">
		<object type="Chicken" />
	</property>
</object>

Вы всегда можете явным образом интегрировать свойства с элементом property, но поскольку свойство идентифицируется по имени, это довольно хрупкий подход. Если вы позднее переименуете свойство, большинство инструментов рефакторинга не смогут идентифицировать и изменять значение атрибута name в различных XML-файлах. Это может привести к ошибкам во время выполнения.

Наилучшее решение – автоматически интегрировать объекты.

Автоматическое интегрирование свойств

Как вы помните из раздела 12.1.2 "Конфигурирование контейнера", вы должны явно разрешить автоматическое интегрирование в Spring.NET, но как только вы это сделаете, заработает Property Injection. Если зависимость не удовлетворяет условиям, то свойство игнорируется:

<objects xmlns="http://www.springframework.net">
	<object id="Course" type="CaesarSalad"
					autowire="autodetect" />
</objects>

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

Все меняется, как только объект IIngredient становится доступным:

<object id="Course" type="CaesarSalad"
				autowire="autodetect" />
<object type="Chicken" />

Теперь при разрешении объекта Course вы будете получать экземпляр CaesarSalad со свойством Extra, имеющим значение Chicken.

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

Вы видели, как использовать Spring.NET для работы с более сложными API разработки. В общем случае вы всегда можете явным образом конфигурировать интегрирование с помощью XML-конфигурации, но вы также видели и то, что паттерн Property Injection можно сконфигурировать таким образом, что он будет поддерживать возможность автоматической интеграции.