Pull to refresh

StructureMap — краткий справочник для работы (3/3)

Reading time7 min
Views2.3K
Теперь наступило время для:
  • Перехватчики (OnCreation, EnrichWith)
  • Дженерик типы
  • Аттрибуты (DefaultConstructor, ValidationMethod, Все остальные)
  • Немного о тестах

Перехватчики


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

Для постобработки существует два+ метода:
  • OnCreation – принимает в качестве параметра Action, позволяет провести свою (custom) инициализацию объекта. Есть доступ к контексту SturctureMap.
  • EnrichWith – принимает в качестве параметра Function. Может возвращать любой тип совместимый с запрашиваемым.
  • Свои перехватчики


Честно сказать, в простых примерах не очень хорошо получается передать разницу и преимущества использования OnCreation перед EnrichWith и наоборот.

OnCreation


Для демонстрации можно воспользоваться следующим простым классом:
public class ClassS : IClassS {
    public int Counter { get; private set; }

    public void Init() {
        Counter = -100;
    }

    public void Init(IClass1 class1) {
        Counter = -50;
    }

    public void Increase() {
       Counter++;
    }
}


Теперь, можно продемонстрировать использование метода OnCreation.
public class InterceptionsSimple {
    public IContainer Container;

    public InterceptionsSimple() {
        Container = new Container(x => x.For<IClassS>()
                                           .Use<ClassS>()
					   .OnCreation(c => c.Init()));
	}
}

При получении класса из IoC фреймворка мы можем увидеть, что значение Counter равняется -100. Как видите, в применении все очень просто и опять же интуитивно понятно, что будет делать код. Неподготовленный человек сможет прочитать определение класса в StructureMap и сразу понять, что и как здесь происходит.

Так же возможен вариант, когда в метод инициализации надо передать объект сконструированный средствами самого StructureMap. Для этих целей используем тот же перегруженный метод.
public class InterceptionWithContext {
	public IContainer Container;

	public InterceptionWithContext() {
		Container = new Container(x => {
			x.For<IClass1>().Use<Class1>();
			x.For<IClassS>().Use<ClassS>()
				.OnCreation((context, cls) => {
					var class1 = context.GetInstance<IClass1>();
					cls.Init(class1);
				});
			});

  }
}

В данном случае в лямбда-выражении есть доступ к самому контейнеру и можно получать объекты, конструирование которых возложено на StructureMap.

EnrichWith


Данный метод позволяет обернуть конструируемый класс в другой, если возможно приведение типов. Например, пусть будет класс
public class ClassSs : ClassS {
	private readonly ClassS s;

	public int Abs {
		get { return Math.Abs(s.Counter); }
	}

	public ClassSs(ClassS s) {
		this.s = s;
	}
}

Тогда можно будет его получать из StructureMap с помощью следующего кода:

public class InterceptionWithContext2 {
    public IContainer Container;

    public InterceptionWithContext2() {
         Container = new Container(x => {
                                     x.For<IClass1>().Use<Class1>();
                                     x.For<IClassS>().Use<ClassS>()
                                          .EnrichWith(cls => {
                                                   cls.Init();
                                                   return new ClassSs(cls);
                                                         });
                                            });
    }
}

В результате, при попытке получить экземпляр класса реализующего интерфейс IClassS мы будем получать класс ClassSs.

Шаблонные (generic) типы


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

Итак, допустим у нас есть шаблонный адаптер.
public interface IAdapter {
	string SomeMethod();
}

public interface IAdapter<T> : IAdapter {}

public class Adapter<T> : IAdapter<T> {
	public string SomeMethod() {
		return typeof (T).ToString();
	}
}

Так же существует несколько его наследников с конкретными типами
public class StringAdapter : Adapter<string> {}
public class IntAdapter : Adapter<int> {}

Никаких ограничений на типы Т в адаптере не предусмотрены.

Есть несколько способов для работы с шаблонными типами. Самый простой способ это зарегистрировать и вызывать конкретные типы StringAdapter, IntAdapter.
public class GenericTypes {
	public IContainer Container;

	public GenericTypes() {
		Container = new Container(x => {
			x.For(typeof (IAdapter<>)).Use(typeof (StringAdapter));
			x.For<IAdapter<int>>().Use<IntAdapter>();
						});
	}
}

В примере показано два идентичных способа регистрации шаблонного класса.

Вызывать их можно несколькими способами.

Способ раз: Можно попросить контейнер выдать нам адаптер по конкретному типу.
private static string GenericTypesExample() {
	var container = new GenericTypes().Container;
	var stringAdapter = container.GetInstance<StringAdapter>();
	var intAdapter = container.GetInstance<IntAdapter>();

	return stringAdapter.SomeMethod() + " " + intAdapter.SomeMethod();
}

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

Составление контейнера остается тем  же, изменяется способ получения.
private static string GenericTypesExample() {
	var container = new GenericTypes().Container;
	var instance = container
			.ForGenericType(typeof (IAdapter<>))
			.WithParameters(typeof(string))
			.GetInstanceAs<IAdapter>();

	return  instance.SomeMethod();
}

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

Для реального испльзование оно бы выглядело как:
private static string GenericTypesExample<T>() {
	var container = new GenericTypes().Container;
	var instance = container
			.ForGenericType(typeof (IAdapter<>))
			.WithParameters(typeof(T))
			.GetInstanceAs<IAdapter>();

	return  instance.SomeMethod();
}

Конкретный адаптер  будет определяться по типу передаваемого параметра T.

Аттрибуты


StructureMap может быть сконфигурирован до определенной степени с помощью атрибутов. Уже освещался атрибут DefaultCounstructor, который указывает на конструктор, который должен использоваться по умолчанию. Есть атрибуты для указания классов и интерфейсов для регистрации, какие свойства класса автоматически заполнять, а так же задавать методы для валидации.

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

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

ValidationMethod


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

Пусть у нас есть класс, для которого надо задавать определенное свойство. Это может быть строка соединения с базой данных, настройки взаимодействия, сложные связи. В нашем примере ограничимся просто тем, что поле не должно быть пустым.
public class SelfValidation {
	public string Name { get; set; }

	[ValidationMethod]
	public void IsClassBuildCorrectly() {
		if(string.IsNullOrWhiteSpace(Name))
			throw new ArgumentException("Name can't be null or empty");
	}
}

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

Регистрация класса пусть будет проведена следующим образом:
public class ValidationShowcase {
	public IContainer Container;

	public ValidationShowcase() {
		Container = new Container(x => x.ForConcreteType<SelfValidation>());
	}
}

Теперь можно попробовать его получить из контейнера.
private static string ValidationShowcaseExample() {
	var container = new ValidationShowcase().Container;
	container.AssertConfigurationIsValid();

	return "";
}

При вызове метода выше, вы получите StructureMapConfigurationException где будет показаны внутренние исключения вызванные проверками внутреннего устройства StructureMap.

На скриншоте видно, что в сообщении об исключительной ситуации как раз фигурирует наше сообщение.

PluginFamily
Указывает StructureMap, что помеченный тип будет использоваться как тип плагинов. Эквивалентно тому, как если бы мы написали .For<plugintype>.

Можно так же указать тип, который будет возвращаться по умолчанию, и можно конкретно указать, что это будет синглтон.
[PluginFamily("Default", IsSingleton = true)]


Pluggable
Помеченный тип будет включен в коллекцию плагинов, указывает на конкретную реализацию. Эквивалентно использованию .Use<pluggabletype>. Необходимо всегда использовать имя для типа.
[Pluggable("Default")]


SetterProperty
Указывает на то, что помеченное атрибутом свойство нужно инициализировать средствами фреймворка. Они будут являться обязательными и если StructureMap не сможет их инициализировать, то будет ошибка исполнения.

Тесты


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

В StructureMap встроены RhinoMock и Moq фреймворки. Для того, чтобы ими воспользоваться необходимо будет доставить пакет NuGet  structuremap.automocking, после чего можно будет использовать мокирование объектов.

Рассмотрение работы Moq и RhinoMock не входит в рамки статьи.

Заключение


Надеюсь, что у вас появилось желание поподробнее и на практике изучить StructureMap. Еще очень советую посмотреть на исходники проекта, можно подчерпнуть интересные и полезные идеи.

За бортом статьи остались темы по созданию контейнера на основе файла конфигурации. Работа с вложенными контейнерами и зачем они нужны. Я надеюсь, что в недалеком будущем этот пробел будет восполнен.

В первой части были освещены темы:
  • Установка
  • Регистрация (Основа, Профили, Плагины, Сканирование, Внедрение)

Во второй части пойдет речь о:
  • Конструкторы (Простые типы, Конструктор по умолчанию, Составные типы, Приведение типов, Задание аргументов)
  • Свойства (Простое задание свойств, Встроенное задание свойств, Задание свойств фреймворком, Допостроение существующих классов)
  • Время жизни
Tags:
Hubs:
Total votes 9: ↑6 and ↓3+3
Comments0

Articles