Здравствуйте, Аноним, Вы писали:
А> В общем, хочу разобраться с этим вопросом, но никак не могу догнать что это такое и зачем оно нужно. Читал статью на MSDN-не, но мало что понял. Можете рассказать простыми словами что это за зверь и в чем преимущества его использвоания?
Смотри
Есть класс A и зависит от от класса B (использует его).
Например так:
public class A
{
B b = new B();
void foo()
{
//используем b
}
}
public class B
{
}
В этом коде существует несколько проблем.
1)Невозможно тестировать класс A в отрыве от B. Если B работает с БД, то для тестов A вам понадобится база.
2)Временем жизни объекта B управляет А, нельзя например использовать один и тот же объект B в разных местах.
Чтобы победить это нам сначало надо выделить интерфейс B и назвать его IB.
public interface B
{
}
public class A
{
IB b = new B();
void foo()
{
//используем b
}
}
public class B:IB
{
}
Но ни одну из наших проблем это не решило.
Теперь можно применить паттерн Service Locator. Суть этого паттерна состоит в том что имеем фабричный метод который по идентификатору возвращает нужную реализацию интерфейса. В .NET есть IServiceProvider с одном методом GetService, которому параметром передается тип.
Предположим что мы сделали хорошую реализацию IServiceProvider и у нас получился такой код
public interface B
{
}
public class A
{
IB b = (IB)serviceLocator.GetService(typeof(IB)); //serviceLocator - реалзиация IServiceProvider, которая по типу IB возвращает B
void foo()
{
//используем b
}
}
public class B:IB
{
}
Теперь временем жизни B управляет serviceLocator, A не знает о классе B. Можно через serviceLocator подпихнуть любую реализацию IB.
Этот подход называет
Dependency Lookup — поиск зависимостей, это один из вариантов подхода IoC.
Но у нас появилась зависимость от serviceLocator, теперь хоть тестировать A без класса B возможно, но это потребует настройки локатора (возможно совсем нетривиальной).
Если подойти с другой стороны, то можно сделать так стобы класс A не искал зависимости сам, а получал их извне. например через конструктор, свойстви или метод.
public interface B
{
}
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