DEPENDENCY INJECTION НА ПРИМЕРЕ UNITY И NINJECT
Калита РоманTaskManagementSoft
Company Logo
ПЛАН
• Что такое Dependency Injection?• Как проблемы решает и какие преимущества
дает при проектировании• Какие есть формы DI• DI контейнеры в .NET
Качественный код/системаРасширяемый
(extensibility)
Легко сопровождаем
ый (maintainability)
Простой (simplicity)
Читабельный (readability)
Тестируемый (testability)
Код
Некоторые проблемы при проектировании приложений
По версии GoF
Веде
т к
силь
ной
связ
анно
сти
При создании объекта явно указывается класс
Зависимость от апаратных/программных
платформ
Зависимость от представления или
реализации объекта
Зависимость от алгоритмов
Сильносвязанные системы
Сильносвязанные системыподдерживать
расширять
тестироватьпонимать
использовать снова
О каких качествах кода может идти речь?
сложно
Уменьшаем связанность
Абстрактная фабрика
(Abstract factory)
Фабричный метод (Factory method)
Локатор сервиса (Service Locator)
Внедрение зависимостей (Dependency
Injection)
Фабрика, Локатор, Метод
Клиент Фабрика, Локатор, Метод
MyClassIMyInterface
Запрашивает IMyInterface
Создает MyClass
Реализует IMyInterface
Dependency Injection
Клиент DI контейнер
КонфигурацияMyClass :
IMyInterface
запрашивает
читаетсоздает/возвращает
Обявляет зависимости
внедряет
MyClass : IMyInterface
Что такое Dependency Injection?Инверсия зависимостей == Обращение контроля == Внедрение зависимостей == Dependency injection
Это принцип в объектно ориентированом программировании, который означает:
• Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции.
• Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
Цель:• Уменьшить связность
Пример связанного приложенияpublic class Program{ public static void Main(string[] args) { var orderManager = new OrderManager(); orderManager.ProcessOrders(); }}
OrderManager OrdersQueue
OrdersProcessor
Пример связанного приложенияpublic class OrderManager { public void ProcessOrders() { var ordersQueue = new OrdersQueue(); IList<Order> orders = ordersQueue.GetPendingOrders(); if (orders.Count == 0) throw new EmptyOrdersQueueException(); var ordersProcessor = new ManualOrdersProcessor(); foreach (Order order in orders) { ordersProcessor.ProcessOrder(order); } } }
Пример связанного приложенияpublic class OrderManager { public void ProcessOrders() { var ordersQueue = new OrdersQueue(); IList<Order> orders = ordersQueue.GetPendingOrders(); if (orders.Count == 0) throw new NoOrdersException(); var ordersProcessor = new ManualOrdersProcessor(); foreach (Order order in orders) { ordersProcessor.ProcessOrder(order); } } }
Проблемы в примере
[TestClass] public class OrderManagerTests { [TestMethod] public void should_process_orders() { var orderManager = new OrderManager(); orderManager.ProcessOrders(); } }
• Тестируемость
• Привязан к конкрентым сущностям, знает о них и создает их
Применяем принцип вручную
public interface OrdersProcessor { void Process(Order order); }
public interface IOrdersQueue { List<Order> GetPendingOrders(); }
• Будем привязыватся к интерфейсам
• Вынесемзависимости в readonly поля класса, которые будут заполнятся из вне при инстанциировании объекта
Применяем принцип вручную public class OrderManager { private readonly IOrdersQueue ordersQueue; private readonly IOrdersProcessor ordersProcessor;
public OrderManager(IOrdersQueue ordersQueue, IOrdersProcessor ordersProcessor)
{ this.ordersProcessor = ordersProcessor; this.ordersQueue = ordersQueue; }
public void ProcessOrders() { IList<Order> orders = ordersQueue.GetPendingOrders();
if (orders.Count == 0) throw new NoOrdersException(); foreach (Order order in orders) { ordersProcessor.Process(order); } } }
Применяем используя контейнерpublic class OrderManager{ [Inject] public IOrdersQueue ordersQueue { get; set; }
[Inject] public IOrdersProcessor ordersProcessor { get; set; }
public void ProcessOrders() { IList<Order> orders = ordersQueue.GetPendingOrders();
if (orders.Count == 0) throw new NoOrdersException(); foreach (Order order in orders) { ordersProcessor.Process(order); } } }
Ручное DI vs Контейнер• Оба варианта решают проблему класса с
высокой связанностью
• Если вручную, выполнение лишней работы по созданию и наполнению зависимостями
• Если используюя контейнер досутпны возможности по «автоматическому» или «условному» наполнению зависимостями, не требующие никаких усилий
• Время жизни объектов также должно гибко контролироватся, вручную – «лишняя работа»
Формы Dependecy Injection• Constructor Injection
• Property(Setter) Injection
• Method Injection
[Inject]public OrderManager(IOrdersQueue ordersQueue, IOrdersProcessor ordersProcessor){ this.ordersProcessor = ordersProcessor; this.ordersQueue = ordersQueue;}
[Inject]public IOrdersProcessor ordersProcessor { get; }
[Inject]void DoSomeTask(IOrdersQueue orederQueue){ // ...}
Dependecy Injection контейнеры• StructureMap (AltDotNet)http://structuremap.sourceforge.net/Default.htm
• Castle Windsor (AltDotNet)http://www.castleproject.org/container/index.html
• Unity (Microsoft P&P)http://www.codeplex.com/unity
• Ninject (open source)http://ninject.org
• Много других (LinFu, например)
Dependecy Injection используя nInject
• Необходимо определить конфигурациюpublic class OrdersModule : NinjectModule{ public override void Load() { Bind<IOrdersQueue>().To<OrdersQueue>(); Bind<IOrdersQueue>().To<OrdersQueue>(); Bind<IOrdersProcessor>().To<ManualOrdersProcessor>(); Bind<OrderManager>().ToSelf(); }}• Теперь можно использовать, например
property injection[Inject]public IOrdersQueue ordersQueue { get; set; }
Dependecy Injection используя Unity
• Необходимо определить конфигурацию var unityContainer = new UnityContainer() .RegisterType<IOrdersProcessor, ManualOrdersProcessor>() .RegisterType<IOrdersQueue, OrdersQueue>();
OrdersManager manager = unityContainer.Resolve<OrdersManager>();manager.ProcessOrders();
• Теперь можно использовать, например property injection
[Dependency]public IOrdersQueue ordersQueue { get; set; }
“Продвинутая” конфигурация
• Условный биндингBind<IOrdersProcessor>().To<ManualOrdersProcessor>().WhenTargetHas<MyAttribute>();Bind<IOrdersProcessor>().To<ManualOrdersProcessor>()
.Only(When.Context.Target.Name.BeginsWith("Manual"));
var container = new UnityContainer() .RegisterType<IOrdersQueue, OrdersQueue>()
.RegisterType<IOrdersProcessor, ManualOrdersProcessor>(new InjectionConstructor(10));
• Время жизни
Bind<IOrdersProcessor>().To<ManualOrdersProcessor>().InSingletonScope();Bind<IOrdersProcessor>().To<ManualOrdersProcessor>().InThreadScope();
var container = new UnityContainer() .RegisterType<IOrdersProcessor,
ManualOrdersProcessor>(new ContainerControlledLifetimeManager())
Преимущества от использования Dependecy Injection• Разделение конфигурирования связей и
использования объектов• Уменьшается связывание
Абстрактные интерфейсы не меняютсяКонкретные объекты реализуют эти интерфейсыКонкретные объекты проще заменить
• Увеличение мобильности модулей
• Улучшение изоляции объектовУменьшается связностьУвеличивается тестируемостьУвеличивается удобство в поддержке
Ссылки• http://martinfowler.com/articles/injection.html
• http://www.objectmentor.com/resources/articles/dip.pdf
• http://msdn.microsoft.com/en-us/library/aa973811.aspx
• http://ninject.org/
• http://unity.codeplex.com
Спасибо за внимание:)
Top Related