Использование generic-ов в Unity

Unity поддерживает работу с generic-типами. 
Можно в контейнер поместить обобщенный тип,  а потом запросить тип с конкретными параметрами.  Подробнее описано по ссылке http://msdn.microsoft.com/en-us/library/dd203156.aspx

Пример кода, использующего такую фичу.

Рассмотрим обобщенный класс репозитария для работы с данными

public interface IRepository<T>
{
    IQueryable<T> Items();
    void Save(T item);
    void Delete(T item);
}

Реализация этого интерфейса для Entity Framework

public class EFRepository<T> : IRepository<T> where T : EntityObject
{
    ObjectContext _context;
    string _entitySetName;

    public EFRepository(ObjectContext context)
    {
        _context = context;
        var container = _context
            .MetadataWorkspace
            .GetEntityContainer(
                _context.DefaultContainerName, 
                DataSpace.CSpace);
        var edmEntityType = (EdmEntityTypeAttribute)typeof(T)
            .GetCustomAttributes(
                typeof(EdmEntityTypeAttribute), 
                false)
            .Single();

        _entitySetName = container.BaseEntitySets
            .Single(s => s.ElementType.Name == edmEntityType.Name
                && 
                s.ElementType.NamespaceName == edmEntityType.NamespaceName)
            .Name;
    }

    public IQueryable<T> Items()
    {
        return _context.CreateQuery<T>(_entitySetName);
    }

    public void Save(T item)
    {
        switch (item.EntityState)
        {
            case System.Data.EntityState.Detached:
                _context.AddObject(_entitySetName, item);
                goto case System.Data.EntityState.Added;

            case System.Data.EntityState.Added:
            case System.Data.EntityState.Modified:
                _context.SaveChanges();
                break;
            default:
                break;
        }
    }

    public void Delete(T item)
    {
        _context.DeleteObject(item);
        _context.SaveChanges();
    }
}

Есть EF модель с типом контекста  AddressBookEntities и двумя сущностями Person и PhoneNumber и сервис SomeService1, работающий с этими сущностями.
Конструктор сервиса выглядит так:

public SomeService1(IRepository<Person> personsRepository, 
                    IRepository<PhoneNumber> phoneNumbersRepository)
{

}

Чтобы получить от Unity экземпляр сервиса можно написать такой код:

var container = new UnityContainer();
container
    .RegisterType<ObjectContext, AddressBookEntities>(
            new ContainerControlledLifetimeManager(),
            new InjectionConstructor())
    .RegisterType(typeof(IRepository<>), typeof(EFRepository<>));

var service = container.Resolve<SomeService1>();

Unity сам создаст инстансы конкретных типов при сборке зависимостей SomeService1.



Рефакторинг legacy кода для использования Unity

Представим себе код, который был написан без применения принципов IoC.
Например такой

public class AccountManager
{
    public void CreateAccount(string userName, string password)
    {
        var accountStore = new AccountStore();
        accountStore.AddNewAccount(userName, password);
    }

    public bool Authenticate(string userName, string password)
    {
        var accountStore = new AccountStore();
        if (accountStore.IsValidAccount(userName, password))
        {
            Session.CurrentSession.SetAuthToken(userName);
            return true;
        }
        else
        {
            return false;
        }
    }

    public bool IsUserInRole(string userName, string roleName)
    {
        var roleStore = new RoleStore();
        return roleStore
                .GetRolesForUser(userName)
                .Contains(roleName);
    }
}

Классы AccountStore и RoleStore – это персистентные хранилища, а класс Session хранит информацию о текущем сеансе работы пользователя.

Класс AccountManager применяется во многих местах, поэтому  мы не можем сразу вынести все зависимости в конструктор.

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

public class AccountManager
{
    [Dependency]
    public AccountStore AccountStore { get; set; }

    [Dependency]
    public RoleStore RoleStore { get; set; }

    [Dependency("GetCurrentSession")]
    public Func<Session> GetCurrentSession { get; set; }

    public AccountManager()
    {
        AccountStore = new AccountStore();
        RoleStore = new RoleStore();
        GetCurrentSession = () => Session.CurrentSession;
    }

    public void CreateAccount(string userName, string password)
    {
        AccountStore.AddNewAccount(userName, password);
    }

    public bool Authenticate(string userName, string password)
    {
        if (AccountStore.IsValidAccount(userName, password))
        {
            GetCurrentSession().SetAuthToken(userName);
            return true;
        }
        return false;
    }

    public bool IsUserInRole(string userName, string roleName)
    {
        return RoleStore
                .GetRolesForUser(userName)
                .Contains(roleName);
    }
}

В этом коде атрибутами сразу обозначены зависимости.  Эти атрибуты будет анализировать Unity при инъекции зависимостей.
Так как при инъекции компоненты подбираются по типу, то для функцции GetCurrentSession желательно указать дополнительный строковый идентификатор.

Теперь будем прикручивать IoC-контейнер.
Нам сначала понадобится создать синглтон контейнера Unity.
Многие, в том числе и я, считают синглтоны злом, но в данном случае немного зла необходимо.

public static class UnitySingleton
{
    static IUnityContainer _instance = new UnityContainer();

    public static UnitySingleton()
    {
	//Здесь будем конфигурировать контейнер
        _instance
            .RegisterType<AccountStore>()
            .RegisterType<RoleStore>()
            .RegisterInstance<Func<Session>>(
                    "GetCurrentSession",
                    () => Session.CurrentSession);
    }

    public static IUnityContainer Instance
    {
        get
        {
            return _instance;
        }
    }
}

Для регистрации метода получения текущей сессии используется RegisterInstance. Этот метод помещает в контейнер экземпляр объекта, указанный параметром. Контейнер удерживает этот объект все время, но с помощью LifetimeManager это поведение можно изменить.

Теперь в конструкторе AccountManager напишем только одну строчку

public AccountManager()
{
    UnitySingleton.Instance.BuildUp(this);
}

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

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



Используем IoC-контейнер Unity

Unity – IoC-контейнер от Microsoft, разработанный группой Patterns&Practicies. Найти его можно по адресу http://www.codeplex.com/unity. Также Unity включен в состав Enterprise Library.

Будет использоваться версия 1.2, последняя на данный момент.

Итак приступим.
Как завещал дядька Фаулер будем рассматривать использования контейнера на примере программы, работающей  с базой данных фильмов.

Чтобы использовать Unity в своей программе надо подключить сборки Microsoft.Practices.ObjectBuilder2 и Microsoft.Practices.Unity.

Нам надо написать класс, который позволяет искать фильмы по различным параметрам.

Предположим что фильмы описываются классом Movie

public class Movie
{
    public string Title { get; set; }
    public string Director { get; set; }
    public int Year { get; set; }
}

Работу с БД завернем в интерфейс

public interface IMovieRepository
{
    IQueryable<Movie> GetMovies();
}

Теперь можем написать класс, который нам нужен. Следуя принципам IoC будем передавать зависимости через конструктор.

public class MovieFinder
{
    IMovieRepository _repository;

    public MovieFinder(IMovieRepository repository)
    {
        _repository = repository;
    }

    public IEnumerable<Movie> FindByTitle(string q)
    {
        return _repository
                .GetMovies()
                .Where(m => m.Title.Contains(q));
    }
}

В целях тестирования напишем простую реализацию IMovieRepository.

public class InMemoryMovieRepository : IMovieRepository
{
    public IQueryable<Movie> GetMovies()
    {
        return new[]
        {
            new Movie
            {
                Title = "Гарри Поттер и узник Азкабана",
                Director = "Альфонсо Куарон",
                Year = 2004
            },
            new Movie
            {
                Title = "Звездные войны: Эпизод 2 - Атака клонов",
                Director = "Джордж Лукас",
                Year = 2002
            },
            new Movie
            {
                Title = "Властелин колец: Братство кольца",
                Director = "Питер Джексон",
                Year = 2001
            },
        }.AsQueryable();
    }
}

Напишем короткий код, который собирает все вместе.

//Создаем контейнер
var container = new UnityContainer();

//Помещаем в контейнер реализацию для используемого интерфейса
container.RegisterType<IMovieRepository, InMemoryMovieRepository>();

//Собираем нужный объект
var finder = container.Resolve<MovieFinder>();

Обратите внимание, что сам тип MovieFinder необязательно помещать в контейнер.

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

Реализация IMovieRepository для БД будет выглядеть так:

public class EFMovieRepository: IMovieRepository
{
    MoviesContainer _context;

    public EFMovieRepository(MoviesContainer context)
    {
        _context = context;
    }

    public IQueryable<Movie> GetMovies()
    {
        return _context.Movies;
    }
}

Где MoviesContainer – тип контекста EF.

Чтобы использовать этот репозитарий слегка изменим код основной программы.

var container = new UnityContainer();
container
    .RegisterType<IMovieRepository, EFMovieRepository>()
    .RegisterType<MoviesContainer>(new InjectionConstructor());

var finder = container.Resolve<MovieFinder>();

Параметр new InjectionConstructor() для второго вызова RegisterType говорит контейнеру что надо использовать конструктор без параметров для создания объекта этого класса. Если конструктор в классе всего один, то задавать такой параметр не требуется.

В приведенном выше коде есть один недостаток. Все объекты создаются каждый раз при вызове Resolve. Если мы не хотим пересоздавать каждый раз контекст EF (это длительная операция), то можем сделать его синглтоном.
Делается это очень просто, достаточно заменить вызов
.RegisterType<MoviesContainer>(new InjectionConstructor())
на
.RegisterType<MoviesContainer>(
                          new ContainerControlledLifetimeManager(),
                          new InjectionConstructor())

Первый параметр задает LifetimeManager, который, как понятно из названия, управляет временем жизни объекта. ContainerControlledLifetimeManager означает что объект будет жить пока живет контейнер, и для всех вызовов Resolve будет возвращаться один и тот же экземпляр контекста.

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



Основная задача IoC-контейнеров

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

На самом деле основной задачей IoC-контейнеров является декларативное управление временем жизни компонент, и зависимостями между ними.

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



Введение в 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 в разных местах.

Чтобы победить это нам сначало надо выделить интерфейс B и назвать его IB.
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