Введение
Сегодня просмотрел ряд скринкастов от Daniel Cazzulino, в которых он рассказывает о создании с нуля простейшего DI контейнера, что не могло не привлечь моего внимания. Ниже будут приведены примеры из его скринкастов.
Подробнее o IoC/DI контейнерах можно прочитать здесь
Требования
Нам понадобится Visual Studio 2008, 15-20 минут свободного времени и желание узнать как же сделать свой DI контейнер.
Начнём. Funq1
- Для начала создадим проект Class Library в Visual Studio, назовём его Funq1
- Далее добавим в решение проект тестов (мы будем использовать TDD на базе тестов Visual Studio) и назовём его Funq1.Tests, создадим ссылку из него на наш проект Funq1
Funq1.Tests
- В проекте Funq1.Tests создадим тестовый класс ContainerFixture, который будет проверять функционал нашего контейнера.
- Добавим объявление интерфейсов IFoo, IBar, а так же соответственно классов Foo, Bar, для тестирования нашего контейнера:
- public interface IBar { }
- public interface IFoo { }
-
- public class Bar : IBar { }
-
- public class Foo : IFoo
- {
- public IBar Bar { get; private set; }
-
- public Foo(IBar bar)
- {
- Bar = bar;
- }
- }
* This source code was highlighted with Source Code Highlighter.
- Создадим тестовый метод, который проверит умеет ли наш контейнер регистрировать и возвращать объекты, выглядеть он будет примерно следующим образом:
- [TestMethod]
- public void RegisterTypeAndGetInstance()
- {
- var container = new Container();
-
- container.Register<IBar>(() => new Bar());
-
- var bar = container.Resolve<IBar>();
-
- Assert.IsNull(bar);
- Assert.IsTrue(bar is Bar);
- }
* This source code was highlighted with Source Code Highlighter.
- Итак, на тестовый класс ContainerFixture выглядит следующим образом:
- using Microsoft.VisualStudio.TestTools.UnitTesting;
-
- namespace Funq1.Tests
- {
- [TestClass]
- public class ContainerFixture
- {
- [TestMethod]
- public void RegisterTypeAndGetInstance()
- {
- var container = new Container();
-
- container.Register<IBar>(() => new Bar());
-
- var bar = container.Resolve<IBar>();
-
- Assert.IsNull(bar);
- Assert.IsTrue(bar is Bar);
- }
-
- public interface IBar { }
- public interface IFoo { }
-
- public class Bar : IBar { }
-
- public class Foo : IFoo
- {
- public IBar Bar { get; private set; }
-
- public Foo(IBar bar)
- {
- Bar = bar;
- }
- }
- }
- }
* This source code was highlighted with Source Code Highlighter.
Funq1.Container
- Наш тест не проходит, так как Container остается не объявленный, в связи с этим в проекте Funq1 добавляем класс Container
- Объявляем поле, в котором будут храниться соответствия типов:
- Dictionary<Type, object> factories = new Dictionary<Type, object>();
* This source code was highlighted with Source Code Highlighter.
- Добавим метод для регистрации типа в контейнере:
- public void Register<TService>(Func<TService> factory)
- {
- factories.Add(typeof(TService), factory);
- }
* This source code was highlighted with Source Code Highlighter.
- А так же метод, для извлечения типа из контейнера:
- public TService Resolve<TService>()
- {
- object factory = factories[typeof(TService)];
-
- return ((Func<TService>)factory).Invoke();
- }
* This source code was highlighted with Source Code Highlighter.
- На данный момент наш Container выглядит следующим образом:
- using System;
- using System.Collections.Generic;
-
- namespace Funq1
- {
- public class Container
- {
- Dictionary<Type, object> factories = new Dictionary<Type, object>();
-
- public void Register<TService>(Func<TService> factory)
- {
- factories.Add(typeof(TService), factory);
- }
-
- public TService Resolve<TService>()
- {
- object factory = factories[typeof(TService)];
-
- return ((Func<TService>)factory).Invoke();
- }
- }
- }
* This source code was highlighted with Source Code Highlighter.
Funq1.Tests
- Пробуем запустить тесты, но они не проходят, т.к. мы умышленно внесли ошибку в описании метода RegisterTypeAndGetInstance() в строке 17: Assert.IsNull(bar), чтобы убедиться, что тест срабатывает
- Для того, чтобы тест прошёл, следует изменить эту строчку на Assert.IsNotNull(bar);
- Далее нас интересует, а что же с зависимостью в конструкторе, как её необходимо зарегистрировать, для этого мы объявляем тестовый метод ResolveGetsDepedenciesInjected() и заносим следующие объявления:
- container.Register<IBar>(() => new Bar());
- container.Resolve<IFoo>(() => new Foo(container.Resolve<IBar>()));
* This source code was highlighted with Source Code Highlighter.
- Однако данное объявление не понравится нашему компилятору, т.к. в области видимости замыкания не присутствует container, в связи с этим данное объявление придётся немного изменить:
- container.Register<IBar>(c => new Bar());
- container.Register<IFoo>(c => new Foo(c.Resolve<IBar>()));
* This source code was highlighted with Source Code Highlighter.
Funq1
- Соответственно необходимо будет немного изменить наш контейнер, добавив в функции аргумент Container:
- public void Register<TService>(Func<Container, TService> factory)
- {
- factories.Add(typeof(TService), factory);
- }
-
- public TService Resolve<TService>()
- {
- object factory = factories[typeof(TService)];
-
- return ((Func<Container, TService>)factory).Invoke(this);
- }
* This source code was highlighted with Source Code Highlighter.
Funq1.Tests
- После внесенных в контейнер изменений необходимо будет немного поправить наш первый тестовый метод RegisterTypeAndGetInstance() строку 13 на выражение:
- container.Register<IBar>(c => new Bar());
* This source code was highlighted with Source Code Highlighter.
- Далее вернемся к нашему методу ResolveGetsDepedenciesInjected() и добавим к нему извлечение объекта и утверждение-проверку на null, в результате получим:
- [TestMethod]
- public void ResolveGetsDepedenciesInjected()
- {
- var container = new Container();
-
- container.Register<IBar>(c => new Bar());
- container.Register<IFoo>(c => new Foo(c.Resolve<IBar>()));
-
- var foo = container.Resolve<IFoo>() as Foo;
-
- Assert.IsNotNull(foo);
- Assert.IsNotNull(foo.Bar);
- }
* This source code was highlighted with Source Code Highlighter.
Заключение
Вот и подошёл к концу наш увлекательный (надеюсь) урок по созданию с нуля простейшего DI контейнера, что же мы приобрели в процессе:
- Базовое понимание функционирования DI контейнера
- Практические навыки разработки с использованием TDD