Inversion of Control (инверсия управления) — это принцип (набор рекомендаций) для написания слабо связанного кода (каждый компонент системы должен быть как можно более изолированным от других, не полагаясь в своей работе на детали конкретной реализации других компонентов).
Dependency Injection (внедрение зависимостей) — это одна из реализаций этого принципа. Также есть еще Factory Method, Service Locator.

In object-oriented programming (OOP), a factory is an object for creating other objects – formally a factory is a function or method that returns objects of a varying prototype or class[1] from some method call, which is assumed to be "new".[a] More broadly, a subroutine that returns a "new" object may be referred to as a "factory", as in factory method or factory function. This is a basic concept in OOP, and forms the basis for a number of related software design patterns.

The service locator pattern is a design pattern used in software development to encapsulate the processes involved in obtaining a service with a strong abstraction layer. This pattern uses a central registry known as the "service locator", which on request returns the information necessary to perform a certain task. Note that many consider service locator to actually be a anti-pattern.

IoC-контейнер — это библиотека, которая позволяет упростить и автоматизировать написание кода с использованием данного подхода. Пример - Ninject.

Согласно подходу инверсии управления если у нас есть клиент, который использует некий сервис, то он должен делать это не напрямую, а через посредника.
Схема взаимодействия клиента с сервисом
То как технически это будет сделано и определяет каждая из реализаций подхода IoC.
Мы будем использовать DI, на простом примере:
Скажем есть некий класс, который создает расписание, а другой класс его отображает (их как правило много, скажем один для десктоп-приложения, другой для веба и т.д.).
Если бы мы ничего не знали о IoC, DI мы бы написали что-то вроде этого:
class ScheduleManager
{
public Schedule GetSchedule()
{
// Do Something by init schedule... 
}
}

class ScheduleViewer
{
private ScheduleManager _scheduleManager = new ScheduleManager();
public void RenderSchedule()
{
_scheduleManager.GetSchedule();
// Do Something by render schedule... 
}
}

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

Воспользуемся внедрением зависимостей (DI) для того, чтобы разорвать этот клубок стальных ниток — сделаем связь между этими классами более слабой, добавив прослойку в виде интерфейса IScheduleManager. И будем разрешать ее одним из способов техники DI, а именно Constructor Injection (помимо этого есть Setter Injection и Method Injection — если в двух словах, то везде используется интерфейс вместо конкретного класса, например в типе свойства или в типе аргумента метода):
interface IScheduleManager
{
Schedule GetSchedule();
}

class ScheduleManager : IScheduleManager
{
public Schedule GetSchedule()
{
// Do Something by init schedule... 
}
}

class ScheduleViewer
{
private IScheduleManager _scheduleManager;
public ScheduleViewer(IScheduleManager scheduleManager)
{
_scheduleManager = scheduleManager;
}
public void RenderSchedule()
{
_scheduleManager.GetSchedule();
// Do Something by render schedule... 
}
}

И далее там где мы хотим воспользоваться нашим классом для отображения расписания мы пишем:
ScheduleViewer scheduleViewer = new ScheduleViewer(new ScheduleManager()); 

Вот уже почти идеально, но что если у нас много различных ScheduleViewer, разбросанных по проекту, которые использует всегда именно ScheduleManager (придется его руками каждый раз создавать) и/или мы хотим как-либо настроить поведение, так что бы в одной ситуации везде использовать ScheduleManager, а в другой скажем AnotherScheduleManager и т.д.
Решить эту проблему как раз и призваны IoC-контейнеры.

IoC-контейнеры


Они помогают уменьшить количество рутины, позволяя задать соответствие между интерфейсом и его конкретной реализацией, чтобы потом везде этим пользоваться.
Как я уже говорил выше, мы будем рассматривать это на примере Ninject —
1. Сначала мы создаем конфигурацию контейнера:
class SimpleConfigModule : NinjectModule
{
public override void Load()
{
Bind<IScheduleManager>().To<ScheduleManager>();
// нижняя строка необязательна, это поведение стоит по умолчанию:
// т.е. класс подставляет сам себя
Bind<ScheduleViewer>().ToSelf();
}
}

Теперь везде где требуется IScheduleManager будет подставляться ScheduleManager.
2. Создаем сам контейнер, указывая его конфигуратор:
IKernel ninjectKernel = new StandardKernel(new SimpleConfigModule());
// там где нужно создать экземпляр ScheduleViewer мы вместо new, делаем так:
ScheduleViewer scheduleViewer= ninjectKernel.Get<ScheduleViewer>();

Контейнер сам создаст экземпляр класса ScheduleManager, вызовет конструктор ScheduleViewer и подставит в него свежесозданный экземпляр ScheduleManager.


Comments and questions

Publish comment or question

Copyright 2019 © ELTASK.COM
All rights reserved.