Обратная связь

Для меня очень важно получать обратную связь от Bас. Пишите мне, если есть вопросы, интересные темы для обсуждения или по любым другим поводам. Мой  почтовый ящик и гугл-группа.

Blog on Medium

I started an English-language blog on the Medium. I will be glad if you join https://medium.com/@alexander.byndyu

суббота, 12 декабря 2009 г.

Принцип инверсии зависимости

Формулировка:

  • Модули верхнего уровня не должны зависеть от модулей нижнего уровня. Оба должны зависеть от абстракции.
  • Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Сейчас мы создадим и отрефакторим приложение. Будем двигаться по шагам и я покажу применение принципа инверсии зависимостей в действии.

Шаг 1. Сильная связанность

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

public class Program 
{
    public static void Main()
    {
        var reporter = new Reporter();
        reporter.SendReports();
    } 
}

Главный объект в нашей бизнес-логике – Reporter.

public class Reporter 
{
     public void SendReports()
     {
        var reportBuilder = new ReportBuilder();
        IList<Report> reports = reportBuilder.CreateReports();
 
        if (reports.Count == 0)
            throw new NoReportsException();
 
        var reportSender = new EmailReportSender();
        foreach (Report report in reports)
        {
            reportSender.Send(report);
        }
    }
}

Устроен Reporter очень просто. Он просит ReportBuilder создать список отчетов, а потом один за другим отсылает их с помощью объекта EmailReportSender.

Есть ли в этом коде проблемы? В подавляющем большинстве случаев зависит от того, кто и как этот код будет использовать, как часто он будет меняться и т.д. Но есть проблемы, которые очевидны уже сейчас.

Тестируемость

Как протестировать функцию SendReports? Давайте проверим поведение функции, когда ReportBuilder не создал ни одного отчета. В этом случае она должна создать исключение NoReportsException:

public class ReporterTests 
{
     [Fact]
     public void IfNotReportsThenThrowException()
     {
         var reporter = new Reporter();
         reporter.SendReports();
         // ???
     }
}

Как в этом случае задать поведение объектов, которые использует Reporter? Мы же должны «сказать» ReportBuilder'у вернуть пустой список, и тогда функция SendReports выбросит исключение. Но в текущей реализации Reporter'а сделать мы этого не можем. Получается, мы не можем задать такие входные данные, при которых SendReports выкинет исключение. Значит в данной реализации объект Reporter очень плохо поддается тестированию.

Связанность

Дело в том, что функция SendReports, кроме своей прямой обязанности, слишком много знает и умеет:

  • знает, что именно ReportBuilder будет создавать отчеты
  • знает, что все отчеты надо отсылать через email с помощью EmailReportSender
  • умеет создавать объект ReportBuilder
  • умеет создавать объект EmailReportSender

Здесь нарушается принцип единственности ответственности. Проблема заключается в том, что в данный момент внутри функции SendReports объект ReportBuilder создается оператором new. А если у него появятся обязательные параметры в конструкторе? Нам придется менять код в классе Reporter да и во всех других классах, которые использовали оператор new для ReportBuilder'а.

К тому же, первые пункты нарушают принцип открытости/закрытости. Дело в том, что если мы захотим с помощью нашей утилиты отсылать сообщения через SMS, то придется изменять код класса Reporter. Вместо EmailReportSender мы должны будем написать SmsReportSender. Еще сложнее ситуация, когда одна часть пользователей класса Reporter захочет отправлять сообщения через emal, а вторая через SMS.

Обратите внимание, что наш объект Reporter зависит не от абстракций, а от конкретных объектов ReportBuilder и EmailReportSender. Можно сказать, что он "сцеплен" с этими классами. Это и объясняет его хрупкость при изменениях в системе. Может оказаться, что Reporter жестко зависит от двух классов, эти два класса зависят еще от 4х других. Получится, что вся система – это клубок из стальных ниток, который нельзя ни изменить, ни протестировать. Этот пример наглядно показывает нарушение принципа инверсии зависимостей.

Шаг 2. Применяем принцип инверсии зависимостей

Сейчас несколькими простыми действиями мы решим наши проблемы с Reporter'ом.

Для начала вынесем интерфейсы IReportSender из EmailReportSender и IReportBuilder из ReportBuilder.

public interface IReportBuilder 
{
    IList<Report> CreateReports();
}
 
public interface IReportSender 
{
    void Send(Report report);
}

Теперь вместо того, чтобы создавать объекты в функции SendReports, мы передами их объекту Reporter в конструктор:

public class Reporter : IReporter 
{
     private readonly IReportBuilder reportBuilder;
     private readonly IReportSender reportSender;
 
     public Reporter(IReportBuilder reportBuilder, IReportSender reportSender)
     {
         this.reportBuilder = reportBuilder;
         this.reportSender = reportSender;
     }
 
     public void SendReports()
     {
        IList<Report> reports = reportBuilder.CreateReports();
 
        if (reports.Count == 0)
            throw new NoReportsException();
 
        foreach (Report report in reports)
        {
            reportSender.Send(report);
        }
    }
}

Во время создания объекта Reporter в самом начале программы мы будем задавать конкретные IReportBuilder и IReportSender и передавать их в конструктор:

public static void Main()
{
     var builder = new ReportBuilder();
     var sender = new SmsReportSender();
     var reporter = new Reporter(builder, sender);
 
     reporter.SendReports();
}

Посмотрим, какие проблемы мы смогли решить.

Тестируемость

Теперь у нас есть возможность передавать в конструктор Reporter'а объекты, которые реализуют нужные интерфейсы. Давайте подставим mock-объекты и зададим нужное нам поведение:

public class ReporterTests 
{
     [Fact]
     public void IfNotReportsThenThrowException()
     {
        var builder = new Mock<IReportBuilder>();
        builder.Setup(m => m.CreateReports()).Returns(new List<Report>());
 
        var sender = new Mock<IReportSender>();
 
        var reporter = new Reporter(builder.Object, sender.Object);
 
        Assert.Throws<NoReportsException>(() => reporter.SendReports());
    }
}

Тест прошел! Мы отлично справились. Теперь есть возможность задавать поведение объектов, с которыми работает наш Reporter. И в данном случае нам не важно, что где-то есть EmailReportSender, SmsReportSender или еще какой-то *ReportSender. Тесты Reporter'а не зависят от других реализаций, мы используем только интерфейсы. Это делает тесты более устойчивыми к изменениям в системе.

Связанность

Мы реализовали на практике главный принцип инверсии зависимостей. Наш Reporter зависит только от абстракций (интерфейсов).

Как быть, если мы хотим отсылать отчеты не через email, а через SMS? Теперь сделать это проще простого . Надо передать в конструктор Reporter'а не EmailReportSender, а SmsReportSender. Код самого Reporter'а мы изменять уже не будем.

Тем не менее меня такое решение еще не полностью устраивает. Мне не нравится, что где-то в клиентах моей библиотеки будет куча строк типа:

var builder = new ReportBuilder(); 
var sender = new SmsReportSender(); 
var reporter = new Reporter(builder, sender); 
 
// ...  

Первым решением может стать использование фабрики объектов Reporter. В принципе это рабочее решение, но мы пойдем еще дальше. Я хочу, чтобы конфигурирование объектов моей программы происходило один раз и находилось в одном месте.

Шаг 3. Используем ServiceLocator

Наша цель - задавать соответствие интерфейсов и их реализаций. Сделаем наше приложение конфигурируемым на клиенте!

Нам нужен объект, который будет хранить информацию о том, что интерфейсу IReportSender соответствует реализация EmailReportSender. Назовем этот объект ServiceLocator. Связь интерфейса и реализации он будет хранить во внутреннем словаре:

public static class ServiceLocator 
{ 
    private static readonly Dictionary<Type, Type> services = new Dictionary<Type, Type>();
 
    public static void RegisterService<T>(Type service)
    {
        services[typeof (T)] = service;
    }
 
    public static T Resolve<T>()
    {
        return (T) Activator.CreateInstance(services[typeof (T)]);
    }
}

Теперь рассмотрим, как мы будем его использовать. Для начала зарегистрируем связи:

public static void Main() 
{
     ServiceLocator.RegisterService<IReportBuilder>(typeof(ReportBuilder))
     ServiceLocator.RegisterService<IReportSender>(typeof(SmsReportSender));

Теперь у класса Reporter создадим конструктор, который пользуется этими настройками:

public class Reporter : IReporter 
{
    private readonly IReportBuilder reportBuilder;
    private readonly IReportSender reportSender;
 
    public Reporter() : this(ServiceLocator.Resolve<IReportBuilder>(), ServiceLocator.Resolve<IReportSender>())
    {
    }
 
    public Reporter(IReportBuilder reportBuilder, IReportSender reportSender)
    {
        this.reportBuilder = reportBuilder;
        this.reportSender = reportSender;
    // ...

Примечание: второй конструктор отлично подойдет для модульного тестирования.

После инициализации ServiceLocator'а вызываем в любом месте программы пустой конструктор:

var reporter = new Reporter(); 
reporter.SendReports();  

С таким подходом мы можем задать соответствие интерфейсов и их реализаций один раз и использовать его. Чтобы во всем приложении вместо SmsReportSender использовать EmailReportSender, надо в начале выполнения программы (сайта, сервиса и т.д.) изменить:

ServiceLocator.RegisterService<IReportSender>(typeof(SmsReportSender));

на другую реализацию IReportSender'а:

ServiceLocator.RegisterService<IReportSender>(typeof(EmailReportSender));

В чем же отличие? Дело в том, что раньше классы сами знали от каких объектов они зависят и могли напрямую использовать друг друга:

Теперь объекты знают только про интерфейсы классов, с которыми взаимодействуют, а реализации просят у сервиса:

Используем готовый IoC (inversion of control) контейнер

Конечно, решение получилось достаточно гибким, но сама реализация класса ServiceLocator еще требует доработки. Например, что делать, если реализацию интерфейса IReportBuilder напротяжении жизни приложения нужно создавать только один раз, а потом при обращении к функции Resolve возвращать созданную реализацию? Нашему ServiceLocator'у не хватает настроек поведения. К счастью, изобретать свой велосипед не надо. На данный момент существуют довольно много готовых IoC контейнеров. Вот самые популярные:

Вкратце, покажу преимущества от их использования на примере Ninject.

Для начала отчистим класс Reporter от лишнего конструктора, оставим только конструктор с параметрами:

public class Reporter : IReporter
{
    private readonly IReportBuilder reportBuilder;
    private readonly IReportSender reportSender;
 
    public Reporter(IReportBuilder reportBuilder, IReportSender reportSender)
    {
        this.reportBuilder = reportBuilder;
        this.reportSender = reportSender;
    }
 
    // ...

Теперь вначале исполнения программы инициализируем контейнер и вызываем отправку отчетов:

public class Program
{
    public static void Main()
    {
        IKernel kernel = new StandardKernel(new InlineModule(
                            m => m.Bind<IReportBuilder>().To<ReportBuilder>(),
                            m => m.Bind<IReportSender>().To<EmailReportSender>(),
                            m => m.Bind<Reporter>().ToSelf()
                            ));
 
        var reporter = kernel.Get<Reporter>();
 
        reporter.SendReports();
    }
}

При создании экземпляра Reporter Ninject с помощью метода Get сам подставит в конструктор объекта реализации IReportBuilder и IReportSender. Это инжекция в конструктор. Есть и другие способы инжектирования зависимостей. Я советую использовать готовые IoC контейнеры в своих проектах.

Инвертированная архитектура

Итак, рассмотрев детали мы можем подняться выше. Взглянем на архитектуру приложения. Давайте рассмотрим классическую трехзвенную архитектуру:

Высокоуровневые модули приложения не отделены от низкоуровневых реализаций. Абстракции не отделены от деталей. Изменение логики в слое доступа к данным может неожидано привести к поломке в модуле отображения. Тестировать такую систему будет очень сложно. Даже если получится написать модульные тесты, то любое изменение в системе приведет к тому, что эти тесты придется переписывать. В результате эта система обладает характеристиками:

1. Жесткость

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

2. Хрупкость

Когда вы вносите изменения в одну часть системы, то в неожиданном месте ломается другая.

3. Неподвижность

Очень сложно повторно использовать код в другом приложении, потому что модули сильно связаны между собой. Внизу дана ссылка на статью Джеймса Ковака, который сделал интересную заметку:

"Выберите любой класс на бизнес-уровне, допустим InvoiceService, и просто скопируйте его код в новый проект. Попробуйте его скомпилировать. Скорее всего выяснится, что не хватает каких-то зависимостей: Invoice, InvoiceValidator и т. д. Скопируйте и эти классы в проект и повторите попытку. Скорее всего и на этот раз каких-то классов система недосчитается. И когда, в конечном итоге, вам все же удастся скомпилировать приложение, вы обнаружите, что в новом проекте находится добрая доля исходного кода."

К чему мы пришли:

В данном случае каждый слой отдельно представлен абстрактными классами/интерфейсами. Сам слой наследуется от этого абстрактного слоя (например, Business Layer реализует интерфейсы, которые объявлены в Business Layer Abstract). Все классы верхнего уровня используют нижележащий уровень через его абстрактный слой. Таким образом ни один слой не зависит от деталей другого. Напротив, они зависят только от абстракций.

Тут есть вопрос по реализации. Как класс из UI Layer узнает во время исполнения программы, какую реализацию IReportSender'а надо использовать? Ведь у него нет доступа к слою Business Layer. Ответ уже был дан выше – мы запишем все зависимости в IoC конейнер. Потом вызываем container.Get(). А там уже по цепочке через инжекцию (например, в конструктор) создадутся все необходимые объекты.

Исходный код

Этот пост входит в серию Принципы проектирования классов (S.O.L.I.D.):


Ссылки

Роберт Мартин: The Dependency Inversion Principle

dnrTV: James Kovacs' roll-your-own IoC container

Приручите программные зависимости для большей гибкости приложений

Inversion of Control Containers and the Dependency Injection pattern

Использование инверсии управления (IoC) в сигнатурах методов

Инверсия зависимостей при проектировании Объектно-Ориентированных систем

NDC 2009, Ayende Rahien - Inversion of Control & Dependency Injection Breaking out from the Dependency Hell

Wikipedia: Dependency injection

37 комментариев:

  1. Отличный пост!
    Только исправь во второй строке кода после фразы: "Чтобы во всем приложении вместо SmsReportSender использовать EmailReportSender, надо в начале выполнения программы (сайта, сервиса и т.д.) изменить..." c
    (typeof(SmsReportSender));
    на
    (typeof(EmailReportSender));

    ОтветитьУдалить
  2. Соглашусь. Пост отличный, хорошо все расписано.

    ОтветитьУдалить
  3. А можно вопрос ;) незнаю даже как сформулировать ;)

    Вот везде в DDD советуют выносить получение entity в отдельный репозиторий, ну тоесть писать

    чтото вроде:
    Product product = productRepository.getbyid(id);

    а не вот так:
    Product product = new Product(id)
    product.load();

    Но такое движение как-то мне не нравится. Это конечно хорошо, что DB код вынесен в репозиторий,
    а в самой entity осталась только бизнесс логика. Но допустим у нас есть класс Order, который этот Product и использовал,
    и вот теперь ему придется знать не только про товар, но еще и про то, что надо использовать репозиторий этих товаров.
    Была одна зависимость - стало их две.

    Есть идея дублировать методы в таком духе, а репозитории вообще делать internal

    class Product
    {
    static Product getbyid(int id)
    {
    //productRepository заинжектили
    return productRepository.getbyid(id);
    }
    static Product getByBarcode(string barcode)
    {

    return productRepository.getbybarcode(barcode);
    }
    static Boolean save()
    {
    return productRepository.save(this);
    }

    }

    Ну и использовать соотвественно вот так

    Product product = Product.getbyid(id);
    product.price=1;
    product.save();

    Как думаешь это плохой подход? какие могут проблемы потом с таким кодом?

    ОтветитьУдалить
  4. @betspaam

    Привет!
    Я уже писал, что думаю по поводу ActiveRecord

    http://blog.byndyu.ru/2009/10/blog-post.html

    Ты писал
    > Но допустим у нас есть класс Order, который этот Product и использовал
    > Была одна зависимость - стало их две

    Приведи пример в коде, иначе не понятно о чем ты говоришь. Можешь мне на мыло написать.

    ОтветитьУдалить
  5. Александр, у меня вопрос. Можно считать, что ServiceLocator является неотъемлемой частью паттерна инверсии зависимости, или это все же полезное дополнение, усиливающее эффект паттерна? Если мы попробуем отказаться от ServiceLocator и вернуться к соотв. фрагменту кода:

    1: var builder = new ReportBuilder();
    2: var sender = new SmsReportSender();
    3: var reporter = new Reporter(builder, sender);

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

    ОтветитьУдалить
  6. @Sergey
    Отличный вопрос!

    Начну по порядку.

    > неотъемлемой частью паттерна инверсии зависимости...
    Поправлю, что это не шаблон проектирования, а принцип. Шаблоны заточены под решение более конкретных задач. Их особенностью является то, что при использовании шаблонов мы не нарушаем ни один принцип проектирования.

    > Можно считать, что ServiceLocator является неотъемлемой частью паттерна инверсии зависимости...
    ServiceLocator это шаблон проектирования. Т.е. это один из способов добиться нужного результата. Это такой же инструмент, как и IoC контейнеры.

    > создание объекта Reporter происходит не в ф-ии Main, а в методе какого-то класса X нашей бизнес-логики, тогда будет ли какая-то польза от такой инверсии?
    Определенно будет. Если ваши объекты будут зависеть от интерфейсов, то архитектура вашего приложения от этого только выиграет.

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

    1: var builder = new ReportBuilder();
    2: var sender = new SmsReportSender();
    3: var reporter = new Reporter(builder, sender);

    можно спрятать в фабрике класса Reporter и тем самым скрыть зависимость от реализаций в одном месте.

    ОтветитьУдалить
  7. Тогда мы локатор заменяем фабрикой. Т.е. в любом мы должны сочетать IoC либо с локатором либо с фарикой, чтобы сделать все части модуля не связанными с конкретными типами? А что по поводу IoC в чистом виде?

    По семантики слов "паттерн/шаблон" и "принцип": я правильно понимаю, что IoC вы считаете "принципом", а локатор и IoC контейнеры -- "паттернами"? В словаре у слова pattern очень широкий набор значений (образец, модель, пример, шаблон, принцип, система, структура, характер, стиль и т.д.), поэтому тут есть шанс основательно запутаться.

    ОтветитьУдалить
  8. Сделаю попытку дать свой вариант ответа на свой же вопрос. Поскольку я мало что читал и применял, не исключено, что мое заключение может быть ошибочным:
    IoC без сочетания с фабриками/локаторами позволит ослабить связи внутри модуля только до определенной степени. Фактически этого же можно добиться, если просто заменить явные типы их интерфейсами везде, где только можно.

    ОтветитьУдалить
  9. @Sergey
    Для начала советую посмотреть/почитать ссылки, которые даны в конце статьи.

    > IoC без сочетания с фабриками/локаторами позволит ослабить связи внутри модуля только до определенной степени...
    Я еще раз повторюсь. Принцип проектирования - это стратегия. Шаблоны - это тактика. Какую тактику вы примените для того, чтобы добиться желаемой архитектуры зависит от вас.
    Я рекомендую использовать IoC-контейнеры, как наиболее простой и эффективный способ.

    ОтветитьУдалить
  10. Код выделяется вместе с номерами строк.
    Плохо.

    ОтветитьУдалить
  11. И что используется для тестирования? атрибуты намекают на то, что это не MsTests.

    ОтветитьУдалить
  12. @reviews

    "И что используется для тестирования?"

    xUnit

    ОтветитьУдалить
  13. Считается, что глобальное состояние, от которого все зависят — это плохо. Но ведь рассуждения, приводящие к такому выводу, можно применить и к IoC-контейнеру? В частности, ваш ServiceLocator — чем не глобальная переменная?

    ОтветитьУдалить
  14. @plain_fact
    Все правильно. ServiceLocator - это глобальная статическая переменная, поэтому я его и заменил за IoC-контейнер.

    ServiceLocator можно считать одним из этапов развития инжектирования зависимостей. Он являет собой пассивное внесение зависимостей.

    ОтветитьУдалить
  15. Александр, вам блог у меня уже был в избранном, но только сейчас нашел время начать его читать. И это было первая статья.

    Спасибо!! Отличный пост. Все идеально расписано. Буду изучать дальше :)

    ОтветитьУдалить
  16. @Maxim
    Рад, что полезно :)

    Теперь мы можем решать проблемы с кодом вместе, присоединяйтесь http://blog.byndyu.ru/2010/05/blog-post.html

    ОтветитьУдалить
  17. Интересная статья, спасибо. Я тоже изложил свое видение IoC: http://dotsid.blogspot.com/2009/12/ioc.html

    ОтветитьУдалить
  18. Небольшое замечание.
    После рефакторинга первоначальной версии программы класс Reporter унаследован от интерфейса IReporter, который нигде не описан.

    ОтветитьУдалить
  19. ServiceLocator надругается над принципом KISS. Почему недостаточно обойтись так:

    class Global {
    public static final IReportBuilder reportBuilder;
    public static final IReportSenderr reportSender;
    static {
    reportBuilder = new ReportBuilder();
    reportSender = new SmtpReportSender();
    }
    // это const-вариант, можно и с getter/setter
    ...
    }


    Какое преимущество, перевешивающие KISS, дают IoC контейнеры?

    ОтветитьУдалить
  20. @sham-bho

    Это на каком языке программирования?

    Что значит static {} посреди класса?

    ОтветитьУдалить
  21. @Александр Бындю
    на сколько я помню это блок статической инициализации в c#

    ОтветитьУдалить
  22. @Nuts

    Я думаю, что это что-то типа Java, судя по слову final.

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

    Плюс теряется возможность настройки жизненного цикла объекта, что позволяют почти все IoC.

    ОтветитьУдалить
  23. Статья действительно очень интересная и полезная, особенно новичку.

    У меня возник вопрос. Необходимо ли досконально снижать связанность классов? Как узнать, что пора остановиться?

    Допустим, в приведенном классе Reporter осталась зависимость от другого класса Report. Что с этим можно сделать?

    ОтветитьУдалить
  24. @

    Спасибо за вопрос. Предлагаю вам посмотреть статью http://en.wikipedia.org/wiki/Cohesion_(computer_science) и в своих проектах стремиться к функциональность связности элементов.

    ОтветитьУдалить
  25. Непонятно, почему этому принципу было дано название "Инверсия Зависимости"? Под "инверсией" нужно понимать изменение направления на обратное? На мой взгляд вы написали о том как этот принцип применять, но ясное, четкое представление об этом принципе не дали...

    ОтветитьУдалить
  26. Если по-простому, то обратите внимание на первый рисунок в разделе Инвертированная архитектура, а потом на второй. Заметили куда теперь направлены зависимости в коде и что зависимости прерываются на "абстрактных" слоях?

    ОтветитьУдалить
  27. Конечно я заметил что зависимость направлена на более абстрактное представление объектов, на интерфейсы. Такая организация позволяет лучше управлять зависимостью за счет использования интерфейсов, которые обеспечивают независимость от реализации. Здесь, на мой взгляд есть переход на более высокий уровень абстракции, который нужен для управления зависимостью объектов и компонентов системы. Поэтому мне не понятно почему здесь речь идет об инверсии.

    ОтветитьУдалить
  28. День добрый, до меня не сразу дошел смысл названия и что конкретно инвертируется. Более того если попросить меня сформулировать у меня это не получится ) Не могли бы Вы написать о названии этого принципа и что конкретно инвертирует? А главное показать ход мыслей проектировщика согласно этому принципу? С примерами! ;)

    ОтветитьУдалить
  29. Alexander Byndyu5 мая 2012 г., 17:05

    Вроде уже показал с примерами, как было и как стало. Вы изучили код в статье?

    ОтветитьУдалить
  30. В ссылках лежит "Роберт Мартин: The Dependency Inversion Principle" - там он конкретно отвечает на вопрос по поводу названия (6 страница): "One might question why I use the word “inversion”..."

    ОтветитьУдалить
  31. Я тут в качестве белой вороны выступлю. Исходная задача вообще решается на процедурном языке. Решение задачи вводит аж 7 новых сущностей, что, как известно, на грани возможностей человека. А если ещё сущности есть? Введение новых абстракций решает все проблемы, кроме слишком большого числа абстракций. В результате имеем код, для понимания которого требуется существенно больше усилий, чем для понимания изначального. И все для того, чтобы в каком-то гипотетическом случае переписывать меньше тестов. В качестве (не очень доказательного) примера могу привести java компонент mime4j, который с точки зрения паттернов написан идеально, но для того, чтобы воспользоваться возможностью расширения в ООП для шаблонов, приходится наследовать 5 интерфейсов IВсякоеРазное... и копипэйстить кучу кода. То есть я считаю, что пока не указан конкретный содержательный юзкейс (с тестами и сравнением вариантов), обосновывающий введение новых абстракций, подобные надстройки только загромождают и запутывают программу. На мой взгляд, это ситуация короткого одеяла -- закрывая бороду, открываешь ноги.

    ОтветитьУдалить
  32. Вопрос по определению IoC.
    Зачем нужно оговаривать второй пункт ("Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций."), если это очевидное утверждение которое по-определению абстракции всегда выполняется?

    ОтветитьУдалить
  33. Круто когда автор пишет статью по ООП без привязки к технологии. Такие статьи потом можно перечитывать. За это вам большое спасибо! А то любят некоторые делать "на примерах" с какими-нибудь WPF.

    ОтветитьУдалить
  34. Артем Горбатюк3 мая 2015 г., 20:53

    День добрый, жаль что не увидел аргументированного ответа на поставленный вопрос товарищем 2 года назад.
    Я сейчас прибываю в состоянии изучения IoC и ни где не увидел ясных причин для ее использования.

    ОтветитьУдалить
  35. Всё, что может быть понято неправильно, будет понято не правильно. Поэтому лучше уточнить все отношения.

    ОтветитьУдалить
  36. Артем, выше постарался ответить. Если у вас остались вопросы, то буду рад помочь.

    ОтветитьУдалить