Что такое инверсия управления (IoC)?

Может ли кто-нибудь подсказать мне несколько базовых руководств по инверсии управления (IoC)? (желательно .net / c #).

Мне нужен код, чтобы понять его :)

Ответов (7)

Если вы понимаете DIP (принцип инверсии зависимостей), IOC - это торт. Я предлагаю изучить объектно-ориентированные принципы, прежде чем изучать шаблоны. Основные принципы можно сложить, как лего, чтобы получить любой узор. Брэндон Джойс

Нет, IoC не является DDD или TDD.

Статья Мартина Фаулера - хорошее вступление.

Прочтите эту отличную статью Мартина Фаулера.

Также взгляните на статью в Википедии .

Внедрение зависимостей - наиболее известный пример инверсии управления, который, вкратце, превращает ваш код из этого:

public class FileReader { }
public class Application {
    // inflexible concrete dependency
    private FileReader fileReader = new FileReader(); 
}

К:

public interface Reader {}
public class FileReader implements Reader {}
public class Application {
    // flexible abstract dependency injected in
    private Reader reader;

    public Application(Reader reader) { // constructor injection
        this.reader = reader;
    }
    public void setReader(Reader reader) { // setter injection
        this.reader = reader;
    }
}

На ваш вопрос о том, что такое МОК, в Википедии есть довольно подробное объяснение .

Что касается руководств или примеров, это руководство довольно хорошо освещает их и содержит много примеров кода.

IOC, или инверсия управления, в большинстве приложений инвертирует маршрут кода от пользовательского интерфейса к базе данных. Это не полный ответ, но это один из самых простых способов разобраться в концепции.

Если вы хотите изучить IOC, займитесь TDD, так как после инвертирования вы обнаружите, что настраивать тесты намного проще.

Пример:

Типичный поток большинства .NET-приложений, которые я видел, выглядит примерно так:

UserCollection col = BusinessLayer.Class.GetLoggedInUsers();
//Business logic
return col;

тогда дело такое:

UserTable table = DataLayer.Class.GetLoggedInUsers();
return table;

и т.д. Это все псевдокод. Чтобы использовать IOC в этом примере, вы добавляете интерфейс для класса уровня данных, например IUserRepository. Вы можете использовать дженерики, и я бы рекомендовал их под капотом.

Затем вы можете сделать что-то вроде этого:

IUserRepository repository = SetUpRepository();
UserCollection col = BusinessLayer.Class.GetUsers(repository);

Почему это важно? Для тестирования вы можете создать фиктивный репозиторий и передать его бизнес-классу. Макет содержит данные, которые всегда одни и те же, что означает, что вы тренируете свой код, а не тестируете его от начала до конца.

Если вам нужен C#, вот базовый пример на weblogs.asp.net :

Говоря жаргонным языком, инверсия контроля - это модель, которая поддерживает, среди прочего, принцип единственной ответственности.

Чтобы понять, почему все это полезно, вам нужно некоторое объяснение, так что потерпите меня.

Единая ответственность в основном означает, что ваш класс должен быть максимально независимым от других частей системы, чтобы минимизировать влияние изменения одной части на другую. (Вы также можете связать это с тем фактом, что изменение реализации не должно вызывать перекомпиляцию всех файлов в ваших проектах, как, например, при изменении файлов .h в проекте C/C++). Побочный эффект заключается в том, что вы получаете множество маленьких объектов, которые делают только одно, но очень хорошо (в идеальном мире).

Но в большинстве случаев для выполнения своей работы одному объекту нужно разговаривать с другим объектом: это зависит от них.

Первая часть смягчения - отделить реализацию от интерфейсов. Это означает использование интерфейсов (или чисто абстрактных классов, в зависимости от выбранного вами языка), чтобы один объект не был привязан к конкретной реализации другого.

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

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

Использование контейнера IOC в основном помогает вам избавиться от этого беспорядка. Когда вам нужен объект, вы его не «обновляете». Вместо этого вы просите контейнер IOC получить его для вас. Контейнер отвечает за доставку функционального объекта, готового к использованию, независимо от его зависимостей.

Это означает, что вашему классу не нужно знать о зависимостях классов, на которые он опирается, что сокращает путаницу. Более того, ему не нужно знать, какой фактический класс реализует службы, на которые он опирается, что означает, что - Реализация службы может быть определена в другом проекте (dll или другом), поэтому ее изменение никогда не повлияет на ваш класс - Реализация может быть разные в зависимости от контекста (подумайте об изменении базы данных или даже о переходе к веб-сервису для получения информации в зависимости от конфигурации или даже текущего состояния приложения).

Чтобы попытаться ответить на другие ваши вопросы, МОК - это образец. TDD и DDD - это методологии проектирования, поэтому одно нельзя приравнивать к другому. Но IOC - бесценный инструмент для поддержки TDD или DDD.

Я знаю, что аббревиатуру суп и частичные образцы, которые вы можете найти, нелегко найти. Лучший совет, который я могу вам дать, - это попробовать несколько небольших проектов на стороне, прототипов, которые вы БУДЕТЕ отбросить, просто чтобы разобраться в этих вещах. Непростой путь, если вы ищете в нем работу, но оно того стоит, хотя бы с личной точки зрения.

Надеюсь, это немного поможет.

Inversion of Control is the abstraction of logic's structure. One approach (often used synonymously with IoC) is Dependency Injection, which abstracts references between objects. When objects are freed from an application's implementation details, such as which class implements which service, they are free to focus on their core functions.

For example, let's say you have a class that needs to map Foo objects to Bar objects. You might write this:

public class FooMapper
{
    public Bar Map(Foo foo)
    {
        // ...
    }
}

and use it like this:

public class NeedsToMapFoos
{
    public void MapSomeFoos()
    {
        var fooMapper = new FooMapper();

        // ...
    }
}

While valid, this is also rigid. NeedsToMapFoos only cares that mapping occurs, not that it occurs in any specific way.

We can represent the concept of "operation sans implementation" with an interface:

public interface IFooMapper
{
    Bar Map(Foo foo);
}

and declare a dependency on that operation:

public class NeedsToMapFoos
{
    private readonly IFooMapper _fooMapper;

    public NeedsToMapFoos(IFooMapper fooMapper)
    {
        _fooMapper = fooMapper;
    }

    public void MapSomeFoos()
    {
        // ...use _fooMapper...
    }
}

Now NeedsToMapFoos has less knowledge, which means it is less complex. Instead of performing administrative duties, it is free to focus on the business case.

Well-factored objects like this are also more adaptable. Just as diamonds are rigid and clay is malleable, internal structure determines response to change.

Finally, logic written in this style is also flexible. Let's say FooMapper.Map is an expensive operation and should be cached. You can use the decorator pattern to wrap the existing implementation and seamlessly pass it to NeedsToMapFoos :

public class CachingFooMapper : IFooMapper
{
    private readonly IFooMapper _innerMapper;
    private readonly IDictionary<Foo, Bar> _cache;

    public CachingFooMapper(IFooMapper innerMapper)
    {
        _innerMapper = innerMapper;
    }

    public Bar Map(Foo foo)
    {
        // Read bar from _cache. If not there, read from inner mapper and add to _cache.
    }
}

// Usage

new NeedsToMapFoos(new CachingFooMapper(new FooMapper()));