Введение в IoC
(Пост на RSDN)Есть класс A и зависит от от класса B (использует его).
Например так:public class A { B b = new B(); void foo() { //используем b } } public class B { ... }
В этом коде существует несколько проблем.
1)Невозможно тестировать класс A в отрыве от B. Если B работает с БД, то для тестов A вам понадобится база.
2)Временем жизни объекта B управляет А, нельзя например использовать один и тот же объект B в разных местах.
public interface IB { ... } public class A { IB b = new B(); void foo() { //используем b } } public class B:IB { ... }Но ни одну из наших проблем это не решило.
Теперь можно применить паттерн Service Locator. Суть этого паттерна состоит в том что имеем фабричный метод который по идентификатору возвращает нужную реализацию интерфейса. В .NET есть IServiceProvider с одном методом GetService, которому параметром передается тип.
Предположим что мы сделали хорошую реализацию IServiceProvider и у нас получился такой кодpublic interface IB { ... } public class A { //serviceLocator - реалзиация IServiceProvider //которая по типу IB возвращает B IB b = (IB)serviceLocator.GetService(typeof(IB)); void foo() { //используем b } } public class B:IB { ... }
Теперь временем жизни B управляет serviceLocator, A не знает о классе B. Можно через serviceLocator подпихнуть любую реализацию IB.
Этот подход называет Dependency Lookup — поиск зависимостей, это один из вариантов подхода IoC.
Но у нас появилась зависимость от serviceLocator, теперь хоть тестировать A без класса B возможно, но это потребует настройки локатора (возможно совсем нетривиальной).
Если подойти с другой стороны, то можно сделать так чтобы класс A не искал зависимости сам, а получал их извне. Например через конструктор, свойство или метод.public interface IB { ... } public class A { IB b; //Например так public A(IB b) { this.b = b; } //Или так public IB B { {get {return b;} set {b = value;}} //Или так public void SetB(IB b) { this.b = b; } void foo() { //используем b } } public class B:IB { ... }Тогда вызывающий код дожен выгядеть примерно так
... var a = new A(new B()); ...
Теперь у нас A не зависит ни от чего, а все зависимости можно передать например через конструктор, что значительно облегчает тестирование A.
Этот подход называется Dependency Injection — инъекция зависимостей, это другой вариант IoC.
Но в большем масштабе проблемы не решает, только перемещает её на уровень выше, то есть все зависимости перемещаются в вызывающий код.
Если такой подход применить во всей программе, то в итоге метод main (или другая точка входа) будет выглядеть так:
new Program(new A(new B(new C(), new D()), new E()......)И это мы еще не рассматривали управление временем жизни объектов.
На помощь нам приходят IoC-контейнеры.
Сами IoC-контейнеры похожи на ServiceLocator, только делают чуть больше работы.
При запросе объекта какого-то типа у контейнера он решает объект какого типа вернуть. Для каждого типа, зарегистрированного в IoC-контейнере, есть карта зависимостей, то есть описание какие параметры надо передавать в конструктор, каким свойствам надо присваивать и какие методы вызывать чтобы инъектировать зависимости в объект. Карта зависимостей задается внешне или получается с помощью рефлексии.
Кроме того контейнер содержит ассоциации для какого запрошенного идентификатора объект какого типа надо вернуть. В качестве идентификатора чаще всего используется сам тип. Для каждой зависимости запрошенного объекта, контейнер создает дургой объект у которого тоже могут быть зависимости, для них эта операция вызывается рекурсивно.
В принципе контейнер не обязан каждый раз создавать объект, он может управлять его временем жизни.
Хороший IoC контейнер должен возможности чтобы код выше можно было переписать так:
container.Resolve<program>();А также потребуется где-то задать параметры контейнеру, чтобы он по запросу объекта типа IB возвращал объект типа B, для IA возвращал A и так далее.
Обычно параметры контейнера можно задавать как в коде, так и во внешнем конфигурационном файле.
Кроме того IoC-контейнер можно использовать как Service Locator, но такого надо избегать.
Для .NET существует множество контейнеров. От MS — Unity, даже версия для Silverlight уже появилась, autofac, Castle Windsor, StructureMap и Spring.NET. Spring.NET — клон java библиотеки со всеми вытекающими. Куча конфигов в XML, огромное число классов в библиотеке, слабое использование возможностей .NET, лично меня от него воротит после использования достаточно легковесного unity.
Кроме того MS ведет разработку библиотеки MEF. В ней применяются принципы IoC, но для более крупных компонент, а также возможности менять набор компонент в Runtime. MEF будет в составе .NET 4.0