×Закрыть

Инвертируй меня полностью

В этой статье я попытаюсь рассказать про принцип инверсии зависимостей (Dependency inversion principle, далее DIP). В статье будут упомянуты уровни абстракций, поэтому настоятельно рекомендую ознакомиться с этим понятием заблаговременно.

Завязка

Чтобы по-человечески разобраться в DIP, надо раскручивать историю с самого начала — с интерфейсов и принципа «проектируйте на уровне интерфейсов, а не реализаций». Не поленитесь, прочтите — это важно.

Вспоминаем, что интерфейс — это средство осуществления взаимного воздействия; общая граница двух отдельно существующих составных частей, посредством которой они обмениваются информацией (честно списала из Википедии). Короче говоря, вот у нас есть механические наручные часы. И все взрослые люди знают, как читать время, используя интерфейс «циферблат со стрелочками». Я понятия не имею, как оно устроено внутри, какие там шестерёнки-колёсики, пружинки и прочее барахло. Мне не надо знать о богатстве внутреннего мира этого чуда инженерии. Я лишь знаю, что все механические часы поддерживают интерфейс «циферблат со стрелочками», и пользуюсь этим. Происходит абстрагирование от деталей реализации.

То есть, интерфейс — это абстракция. Давайте взглянем на это как на концепцию. Когда мы что-то проектируем, по сути нам важно лишь знать составные части системы и что они умеют делать. Как именно они умеют это делать — в момент конструирования никого не колышет. Выражаясь более заумно, нас интересуют уровни абстракции (и перечень элементов, находящихся в каждом уровне), а также их интерфейсы.

Все классы надо рассматривать как абстракции, обладающие своими интерфейсами. Это и значит проектировать на уровне интерфейсов, а не реализаций. Какую именно конструкцию языка в дальнейшем мы используем, Abstract Class или Interface, — по сути также не важно.

Если желаете, можно взглянуть на этот принцип и под другим углом: нам не обязательно знать, с каким конкретным классом (реализацией) мы имеем дело (часы фирмы такой-то, модель такая-то). Достаточно знать, какой у него суперкласс, чтобы пользоваться его методами (Abstract Class или Interface, в нашем примере это циферблат со стрелочками).

Кульминация

А теперь настало время чудес: я приведу наглядный образчик проектирования с кусками кода. Так как моим основным языком программирования является PHP (простите, так вышло), то и примеры я адаптирую под особенности этого языка.

Итак, любой музыкальный инструмент производит звуки (не важно какой именно — шумит себе и всё). Конструируем:

Например, это может быть барабан:

Или гитара, или губная гармошка, да что угодно. Но вот когда мои друзья-хипстеры решают, что мне непременно в жизни не хватает чего-то эдакого и сообщают, что подарят мне неведомый музыкальный инструмент, — во мне пробуждается непоколебимая уверенность, что я таки смогу извлечь из него хоть какой-то звук. Хотя я и не знаю заранее, что же за инструмент это будет.

Вот мы и сконструировали ряд классов, акцентируясь на том, что они умеют (т.е. на интерфейсах).

А теперь давайте немного усложним наш пример и продолжим проектировать на уровне интерфейсов. Мои друзья решили сэкономить и взять, что там они выбрали, в магазине подержанных инструментов. В нём перед продажей все инструменты ремонтируются и натираются до блеска (repair), а также заворачиваются в упаковку индивидуальной формы (pack). Очевидно, что инструменты разных производителей будут по-разному чиниться и по-разному упаковываться. На одном дыхании пишем следующие классы для нашей задачи.

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

Например вот такая губная гармошка фирмы Marys (только что придумала):

А также нам нужен магазин подержанных инструментов, который подготавливает инструменты к продаже:

Набор классов, мягко говоря, весёлый, но для примера нам подойдёт.

Мы не нарушали принципа «проектировать на уровне интерфейсов, а не реализаций». Мы создавали классы, концентрируясь на их способностях. Однако, давайте пристально взглянем на последний класс Pawnshop.

Допустим, по какой-то причине в будущем мы решим изменить интерфейс Instrument, в результате чего набор его методов станет другим. Или наш магазин решит вдобавок к подержанным балалайкам приторговывать ещё и абсолютно новыми инструментами, не нуждающимися ни в упаковке, ни в ремонте. Или ещё что-то произойдёт и конкретный музыкальный инструмент перестанет поддерживать знакомый нам интерфейс. Но в работе Pawnshop мы опираемся на надежду, что только что созданный конкретный объект гарантированно будет субклассом Instrument — это совершенно безрассудно. А сколько таких Pawnshop у нас по всему проекту — страшно даже представить.

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

Развязка

Настало время взглянуть на определение принципа инверсии зависимостей, формулировок которого в ассортименте и количестве:
— Код должен зависеть от абстракций, а не от конкретных классов;
— Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций;
— Модули верхних уровней не должны зависеть от модулей нижних уровней. Оба типа модулей должны зависеть от абстракций.

Данный принцип призывает не только проектировать на уровне интерфейсов, но и пресечь беспорядочное использование конструкции new, потому что она создаёт конкретную реализацию. Соответственно, класс, в котором используется new, автоматически становится зависимым от этой конкретной реализации, а не от абстракции. Не смотря даже на то, что мы проектировали на уровне интерфейсов.

Если всё так просто, то почему же этот принцип называется «инверсия зависимостей». Что инвертируется?

Вернёмся к нашим музыкальным инструментам. Хотя мы и строили классы, проектируя их на уровне интерфейсов, всё равно наш класс Pawnshop зависит от конкретных реализаций:

Если мы попытаемся применить DIP, нам нужно будет изолировать все new  внутри некоторой ограниченной области — для этого надо использовать какой-то из порождающих паттернов или Dependency Injection. В результате мы можем получить совершенно другую картину.

Например, можем сделать так:

Или так:

Или ещё как-нибудь. Суть в том, что теперь мы получаем объекты гарантированного типа. Новая схема зависимостей будет выглядеть так:

Стрелки, идущие к конечным реализациям (MarysHarmonica, BillysDrum и др.), поменяли своё направление. Мы инвертировали зависимости. До применения принципа DIP у нас присутствовала зависимость Pawnshop от конкретных классов музыкальных инструментов. Теперь же ничто не зависит от конечных реализаций, всё зависит только от абстракций. За исключением наших «изоляторов», куда мы поместили new. Но изменить механизм создания экземпляров внутри этих ограниченных конструкций гораздо легче, чем рыскать по необъятным просторам кода, выискивая, где же мы наплодили наши вновь изменившиеся объекты.

Данный принцип (ограничение new) не применим к библиотечным классам. Потому что мы не будем их менять никогда. А раз эти классы «хронически неизменны», то и связанные с изменениями риски отпадают.

Итак, коротко говоря, принцип DIP призывает:
— проектировать на уровне интерфейсов;
— локализовать создание изменяемых классов (скажи нет беспорядочным new!).

И вот мы вновь убедились, что ООП — это до тошноты логическая и достаточно простая для понимания вещь.

P.S. Использовав конструкцию php <new $firmName . $instrumentName> я экономлю количество строк кода, акцентируя ваше внимание на происходящем внутри метода. Для дотошных: можете представить себе, что вместо этой строки там написано if-if-if.

P.P.S. Да, да «никто не будет писать такого в боевом проекте». Однако, используя упрощённые примеры, я полагаю, мне удалось продемонстрировать работу принципа DIP.

Лучшие комментарии пропустить

у меня наблюдения диаметрально противоположные ) Большинство моих друзей используют тёмный фон в IDE — так глаза меньше устают.

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

То, что вы пишите про ООП — это плохо. Не плохо иметь свое представление о принципах ООП, не плохо рассуждать и разбираться. Плохо подавать это как прописную истину.

Я надеюсь, что читатели, которые, находятся на начальном этапе изучения сабжа, обратят внимание на то, что написанное иллюстрируется примерами на PHP и обратятся для изучения ООП к чему-то с менее динамической типизацией.

Лично я в статье и в комментариях увидел жесткую passive-aggressive позицию автора, желание самоутвердиться и сочинение на тему «как я вижу ооп».

Будьте добрее.

205 комментариев

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

Наталія, Ви ж пам’ятаєте картинку де зображені мужик, його жінка і осел ...

Всі можливі комбінації учасників того коміксу не влаштували натовп.
Так і тут. Завжди знайдеться «а баба Яга — проти».

Коротше: пєчалька, що сайт де щось цікаве проскакувало перетворюється на сироварню.

Статья хорошая!
А комментаторы в основном опять разочаровывают. Те, кто забывает поблагодарить автора за труд, а только цепляются к статья, а еще и хамят — вы не критики, вы быдло под соусом интеллигенции :)

Способ изложения ок, но вот тема выбрана неудачная.
Dependency injection может неплохо выглядеть в теории. Но на практике она бесполезна и обычно ведет к усложнению кода.

Несколько отрезвляющих вопросов любителям DI:

— Зачем заводить кучу бесполезных интерфейсов ради DI, если за большинством таких интерфейсов на практике редко бывает более одной реализации?
— Как узнать, когда, в каком порядке и с какими аргументами были инициализированы зависимости, использующиеся в конкретной области кода? Как это влияет на debuggability и maintainability кода?
— Как повлиять на порядок инициализации зависимостей и аргументы, используемые при их инициализации?
— Как сделать так, чтобы определенные зависимости создавались при каждом вызове функции, а другие — использовались повторно?
— Какие проблемы решает DI? Стоит ли решение этих проблем добавления геморроя по вышеуказанным вопросам?

Вопросы отрезвляют то что надо , сразу ясно что в высокоуровневым программированием ты дальше hello worlda не читал.
Открой доку к любому di контейнеру и на каждый свой коварный вопрос получишь ответ в списке faq.

Ответьте на коварные вопросы в контексте вашей любимой DI системы.

— Как узнать, когда, в каком порядке и с какими аргументами были инициализированы зависимости, использующиеся в конкретной области кода? Как это влияет на debuggability и maintainability кода?
— Как повлиять на порядок инициализации зависимостей и аргументы, используемые при их инициализации?
— Как сделать так, чтобы определенные зависимости создавались при каждом вызове функции, а другие — использовались повторно?
    public static void Configure()
    {
      var builder = new ContainerBuilder();


      builder.Register(context =>
      {
         var resolver = context.Resolve<IEnviromentResolver>();
        // Диманическое рзрешение в рантайме реализации для зависмости - одна из основных функций контейнера.
        // При разрашении зависимости решаем какую реализацию использовать
        // SQL/REDIS например исходя из урла запроса в веб приложении
         return resolver.IsTest() ? new SqlRepository<User>() :
         new RedisRepository<User>();
      })
        .As<IRepoistory<User>>() // Смотрим какая реализация  интерфеса разрешаеться в зависимости от контекста читая  fluent конфигурацию.
        .InstancePerRequest(); //  Управление жизненным циклом одна  из основных задач решаемых DI контейнерами.
      //PerThread, Static, Transient, PerScope который можно самому кодить.

    } 

Те вопросы которые ты задал — скорее подчеркивают сильные стороны использования контейнера, а не их недостатки.

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

Забыл упомянуть, что для совершенства не хватает xml-конфигурации зависимостей.

Мне твоя ирония пока не понятна. Давай с конкретикой.
У тебя есть необходимость в каждом хендлере ниже получать IRepository для юзера.

RedisRepository здесь:
GET test/users -> AuthorizationHandler -> UsersHandler
GET test/userRoles -> AuthorizationHandler -> UsersHandler
GET test/userSettings -> AuthorizationHandler -> UsersHandler

SqlRepository здесь:
GET auth/users -> AuthorizationHandler -> UsersHandler
GET auth/userRoles -> AuthorizationHandler -> UsersHandler
GET auth/userSettings -> AuthorizationHandler -> UsersHandler

Всего 4 хендлера и 6 маршрутов, в 3 случаях маршрута тебе надо получить зависимость одну во втором другую.
В примере выше это все контролируеться указанной fluent конфигурацией.

Покажи нам как ты решишь разрешение зависимостей с циклом жизни обьекта Http запрос в своей библиотеке — fast http явным инстанцирование в нужных местах. Я знаю что в Go не используют DI — правда интересно как ты будешь управлять зависимостями — может действительно это никому не надо и писать будем как 20 лет назад.

Полагаю тебе есть чем нас удивить на реальных примерах раз ты уже начал подымать эти пункты в своих вопросах на тему DI.

Покажи нам как ты решишь разрешение зависимостей с циклом жизни обьекта Http запрос в своей библиотеке — fast http явным инстанцирование в нужных местах.
Вот так сойдет?
func RequestHandler(ctx *fasthttp.RequestCtx) {
        path := ctx.Path()
        var repository IRepository
        if strings.HasPrefix(path, "/test/") {
                repository = RedisRepository
        } else {
                repository = SqlRepository
        }

        if err := AuthorizationHandler(ctx, repository); err != nil {
                fmt.Fprintf(ctx, "authorization error: %s", err)
                return
        }

        UsersHandler(ctx, repository)
}
Я знаю что в Go не используют DI
Некоторые больные DI головного мозга используют. Тем более, что в Go это намного проще по сравнению с Java и C# - там ведь не нужно писать бесконечные ’inherits Foo, extends Bar’ для классов, реализующих 100500 бесполезных DI-интерфейсов.
может действительно это никому не надо и писать будем как 20 лет назад
А что в этом плохого?

В условии опечатался чуток:

RedisRepository здесь:
GET test/users -> AuthorizationHandler -> UsersTestHandler
GET test/userRoles -> AuthorizationHandler -> UserRolesTestHandler
GET test/userSettings -> AuthorizationHandler -> UserSettingsTestHandler
SqlRepository здесь:
GET auth/users -> AuthorizationHandler -> UsersHandler
GET auth/userRoles -> AuthorizationHandler -> UserRolesHandler
GET auth/userSettings -> AuthorizationHandler -> UserSettingsHandler

+
GET %любой неопределенный роут% -> 404

Некоторые больные DI головного мозга используют.
Так ты же тоже используешь, просто делаешь это без сторонней библиотеки, а пишешь резолв зависимостей прямо в хендлере-враппере, который выполняет роль middleware компонента для DI — один в один по аналогии с тем как DI контейнры это делают в веб проектах на других платформах.
GET %любой неопределенный роут% -> 404

Это делается добавлением одной строчки кода:

func RequestHandler(ctx *fasthttp.RequestCtx) {
        path := ctx.Path()
        var repository IRepository
        if strings.HasPrefix(path, "/test/") {
                repository = RedisRepository
        } else {
                repository = SqlRepository
        }

        if err := AuthorizationHandler(ctx, repository); err != nil {
                fmt.Fprintf(ctx, "authorization error: %s", err)
                return
        }

        if !UsersHandler(ctx, repository) {
            ctx.Error("Page not found", fasthttp.StatusNotFound)
        }
}
Так ты же тоже используешь, просто делаешь это без сторонней библиотеки, а пишешь резолв зависимостей прямо в хендлере-враппере, который выполняет роль middleware один в один по аналогии с тем как DI контейнры это делают в веб проектах на других платформах.

Главное отличие в том, что у меня простой и понятный код, который можно:

  • легко подебажить;
  • менять произвольным образом под новые требования;
  • сколько угодно раз рефакторить.

С DI-решениями эти действия затрудняются:

  • Дебаг может превратиться в nightmare, если придется лезть во внутренности DI-контейнера. Которые обычно намного сложнее вышеприведенного кода. Например, если заглючила какая-то зависимость, логично посмотреть, где, когда и с какими параметрами эта зависимость была инициализирована. Подсказка — это будет где-нибудь в самом запутанном коде внутри DI-контейнера.
  • DI-контейнер хрен поменяешь под новые требования, которые не были предусмотрены его разработчиками. Приходится либо отказываться от DI, либо наворачивать гору костылей. Обычно выбирают второй вариант, т.к. выбор первого ставит под сомнение компетентность того, кто внедрил DI в проект :)
  • Ваш код становится жестко завязан на DI-контейнере, что может затруднить последующий рефакторинг. В итоге ваш код постепенно скатывается в УГ из-за DI.

Там не одна строчка там хендлеры везде разные.читай внимательно.

1. Di контейнер не дебажиться, дебагер проскакивает скомпилированный код библиотек в нормальных языках. Это же не go. Так что формально отладка легче чем дебажиться твой спаггети код.
2. Так же можно сказать про твой fasthttp.
3. Код скатывается в уг не потому что ты библиотеки используешь, а потому что ты велосипеды везде лепишь.

2. Так же можно сказать про твой fasthttp.

У каждого решения есть преимущества и недостатки. Рассмотрим их на примере fasthttp и абстрактного DI-фреймворка.

Преимущество fasthttp: позволяет легко создавать http-сервера, способные обработать сотни тысяч запросов в секунду от миллионов одновременно подключенных клиентов.

Недостаток fasthttp: меньшая устойчивость к некорректному использованию API по сравнению с http сервером из стандартной библиотеки.

Преимущество DI-фреймворка: позволяет легко создавать юнит-тесты, используя мокнутые зависимости.

Недостатки DI-фреймворка:
— Юнит-тесты оторваны от реальности, т.к. они тестируют мокнутый код вместо кода, исполняющегося в продакшн.
— Тесты жестко связаны с внутренней реализацией тестируемых объектов. Малейшее изменение кода внутри объектов требует обновления тестов, даже если внешнее поведение объектов не меняется.
— Куча бесполезных интерфейсов для каждого класса, используемого в зависимостях.
— Неочевидная логика инициализации зависимостей. Это может затруднить ответы на следующие вопросы при дебаге:
* Откуда взялось текущее состояние данных зависимостей?
* Когда, в какой последовательности и с какими параметрами были инициализированы данные зависимости?
* Почему данные зависимости были инициализированы именно с такими параметрами?

— Искуственно созданная слабая связанность кода, который на практике является сильно связанным. Это затрудняет ответы на следующие вопросы, возникающие при инспекции и рефакторинге кода:
* Где используется данная реализация DI-интерфейса?
* Какие реализации могут быть переданы в данной зависимости?
* Какими параметрами инициализируется данная реализация и как она попадает в качестве зависимости в конкретное место?

— Затрудненная/неочевидная инициализация и передача конкретных объектов в качестве зависимостей к определенным классам/методам.

Fluet конфигурацию контейнера для типов — читать намного проще чем просматривать все if() {} в которых ты пропишешь правила инстанцирования в зависимости от контекста. С котейнером это намного очевидней, удобней, и самое главное в разы легче поддерживать чем твой спаггети-код иницализации.

— Юнит-тесты оторваны от реальности, т.к. они тестируют мокнутый код вместо кода, исполняющегося в продакшн.
Юнит тесты предназначены для того что бы тестировать логику конкретного модуля\функции, а не любой код.
Если в твоем коде есть только вызов внешних сервисов — их надо покрывать другим типами тестов, интеграционными например.
Юнит тесты предназначены для того что бы тестировать логику конкретного модуля\функции, а не любой код.

С этим никто и не спорит. Сами по себе юнит-тесты — замечательное средство против багов. Проблема в том, что юнит-тесты в контексте DI-фреймворков превращаются в цирк с тестированием мокнутых зависимостей, а не конкретного модуля. Малейшее изменение последовательности обращений к зависимостям внутри модуля без изменения внешнего поведения приводит к переписыванию «юнит-теста».

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

А без DI они вообще не могут быть написаны. Будут интеграционные, или как там у вас зовутся тесты более высокого уровня.

Малейшее изменение последовательности обращений к зависимостям внутри модуля без изменения внешнего поведения приводит к переписыванию «юнит-теста».

Это плохой мок, если так. Ну или неизбежная плата. Надо оценивать именно в таком контексте — стоит ли оплата поддержкой такого мока самого теста.

Юнит-тесты оторваны от реальности, т.к. они тестируют мокнутый код вместо кода, исполняющегося в продакшн.

Мнэээ... а с чего это вдруг они различаются? На то и тестирование с моками, что код один и тот же. В отличие от окружения.

Тесты жестко связаны с внутренней реализацией тестируемых объектов. Малейшее изменение кода внутри объектов требует обновления тестов, даже если внешнее поведение объектов не меняется.

Как раз классические TDD’шные тесты (и их обновлённый аналог в виде тестов BDD) завязываются только на ТЗ (контракт) к классу/методу/функции, не учитывая особенности реализации (кроме, разве что, степени покрытия). Тесты по специфике реализации по «идеальному» TDD вообще не возможны, потому что реализация ещё неизвестна, когда пишутся тесты. Тесты по более старым/новым методикам, или вне TDD, когда допускается их написание вслед за реализацией, тоже больше ориентируются на отлов граничных случаев, а не внутренних потрохов.
По некоторым авторам, если надо писать тесты типа «белого ящика», это значит, что тестируемая сущность перегружена и её надо делить на части. И в этом есть заметная доля правды.

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

Откуда бы ей взяться, этой куче интерфейсов? Приведи пример.

Неочевидная логика инициализации зависимостей.

Во-первых, во многих случаях очевидная (особенно при классическом подходе «зависимости и параметры для них читаем из конфига»). Во-вторых, как раз инициализация зависимостей (особенно на старте) — то, что очень желательно логгировать в подробностях (нужно всегда, и в продукции даже важнее, чем при отладке).

Это затрудняет ответы на следующие вопросы, возникающие при инспекции и рефакторинге кода:
* Где используется данная реализация DI-интерфейса?

Вопросы понятны, но они ровно такого же уровня проблемности, как любая передача чего-то в другой модуль, особенно по ссылке/указателю. У тебя же нет проблем, когда в функцию передаётся коллбэк, хотя от этого уже становится неизвестно для вызывающего, кого он вызовет, а для вызванного — когда?
Или, например, в случае канала в Go, кто прочтёт записанное в канал? А если запись блокирующая, на сколько будет заблокировано?

Вообще, к DI надо подходить по принципу, аналогичному принципу Страуструпа про самолёт и двигатель — «Чтобы ответить, можно ли наследовать самолёт от двигателя, подумайте про самолёт с двумя двигателями». DI надо вводить там, где есть заметные шансы на использование двух и более вариантов зависимостей, а вот как эти шансы меряются — это уже вопрос личного понимания и опыта.

Мнэээ... а с чего это вдруг они различаются? На то и тестирование с моками, что код один и тот же. В отличие от окружения.

1. В продакшне зависимости настоящие, а не мокнутые.
2. DI-фреймворки пропагандируют использование тупых моков, которые умеют только воспроизводить заранее заданную последовательность обращений к ним. Малейшее отклонение от этой последовательности ломает тесты, даже если внешнее поведение тестируемого модуля не меняется.

Как раз классические TDD’шные тесты (и их обновлённый аналог в виде тестов BDD) завязываются только на ТЗ (контракт) к классу/методу/функции, не учитывая особенности реализации (кроме, разве что, степени покрытия)

Я слабо представляю, как можно совместить TDD и разработку с помощью DI-фреймворков.

Откуда бы ей взяться, этой куче интерфейсов? Приведи пример

Пример приводить не буду — пусть его приведут почитатели DI*. Например, автор этой статьи или Dmitriy Onykyyenko.

Прятать все зависимости за интерфейсами — основная мысль DI*. Ведь это позволяет:

— полностью отвязаться от конкретных реализаций зависимостей;

— в любое время (даже в рантайм) подменять конкретные реализации зависимостей, не меняя использующий их код;

— тестировать с помощью моков.

Правда, почитатели DI* игнорируют следующие факты:

— большинство зависимостей в реальном продакшн коде жестко связаны друг с другом, в отличие от идеального DI* кода. Принудительное разделение этих зависимостей затуманивает логику кода и ведет к нагромождению бесполезных абстракций;

— за большинством DI-интерфейсов в продакшн коде будет всегда одна и только одна конкретная реализация;

— благодаря DI-интерфейсам юнит-тесты превращаются в цирк на дроте, т.к. моки, применяемые в тестах, не имеют ничего общего с конкретными зависимостями, применяемыми в продакшн.

Вопросы понятны, но они ровно такого же уровня проблемности, как любая передача чего-то в другой модуль, особенно по ссылке/указателю. У тебя же нет проблем, когда в функцию передаётся коллбэк, хотя от этого уже становится неизвестно для вызывающего, кого он вызовет, а для вызванного — когда?
Или, например, в случае канала в Go, кто прочтёт записанное в канал? А если запись блокирующая, на сколько будет заблокировано?

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

Я слабо представляю, как можно совместить TDD и разработку с помощью DI-фреймворков.

Ну я вообще слабо представляю себе, как на каноническом TDD можно вообще что-то делать, поэтому и не рассматриваю подобные варианты сколь-нибудь всерьёз. А вот его приземлённые к реальности варианты уже вполне могут и давать практический результат, и использовать DI с конфигурационными фреймворками.

Пример приводить не буду — пусть его приведут почитатели DI*. Например, автор этой статьи или Dmitriy Onykyyenko.

Неее, это будет искусственный вариант. Я спрашивал про естественные случаи, когда невозможно написать без «кучи интерфейсов», причём именно «кучи», а не 2-3 (потому что 2-3 вполне укладываются в необходимости для тестирования по частям).

— за большинством DI-интерфейсов в продакшн коде будет всегда одна и только одна конкретная реализация;

— благодаря DI-интерфейсам юнит-тесты превращаются в цирк на дроте, т.к. моки, применяемые в тестах, не имеют ничего общего с конкретными зависимостями, применяемыми в продакшн.

Это можно переформулировать так: желание тестировать по частям приводит к мокам, а те не могут быть сделаны без DIP+DI; с другой стороны, во многих случаях (может быть, и в большинстве) DI окажется нужен только для подстановки тестового мока вместо единственной боевой реальности. Я правильно понял?

Если да, то моё мнение — если тут вообще нужен тест и он в таком виде окупается (за счёт затрат на лишний резолвинг ссылок), то пусть. Если нет — то нужен или язык, поддерживающий препроцессор, или отдельный препроцессор в среде сборки, чтобы условной компиляцией менять зависимости. И для C/C++ это вполне окупится. Как для твоего Go не знаю — но внешний препроцессор спасёт всегда :)

Ну а что мок не всегда ведёт себя, как реальная компонента — ну извините, это уже просто недоработка мока. Увы, тут альтернативы нет, кроме как не тестировать на этом уровне вообще.

только там, где это действительно необходимо, а не лепить их на каждом шагу, как того требует DI*-идеология.

Ну так «що занадто, то не здраво» должно быть основным принципом везде, а не только там, где обожглись.

DI окажется нужен только для подстановки тестового мока вместо единственной боевой реальности. Я правильно понял?
Бинго!
Ну а что мок не всегда ведёт себя, как реальная компонента — ну извините, это уже просто недоработка мока. Увы, тут альтернативы нет, кроме как не тестировать на этом уровне вообще.

Есть еще вариант проектирования компонента с поддержкой юнит-тестирования. При этом совсем не обязательно прятать все зависимости за тупыми DI-интерфейсами.

Есть еще вариант проектирования компонента с поддержкой юнит-тестирования. При этом совсем не обязательно прятать все зависимости за тупыми DI-интерфейсами.

А как тогда?

Пример.
Есть класс Users, который зависит от класса DB. Для юнит-тестирования Users:

1) создаем тестовую базу (например, inmemory);
2) создаем объект DB, подключенный к этой тестовой базе;
3) передаем DB в Users;
4) тестируем Users.

Шаги 1-2-3 выносим в отдельную функцию, возвращающую объект Users, готовый для тестирования.
Шаг 4 разбиваем на кучу юнит-тестов, проверющих различные аспекты поведения объекта Users.

Уже слышу возражения DI*-поклонников:

1) «Это больше похоже на интеграционное тестирование — ведь в каждом тесте кроме объекта Users присутствует настоящий объект DB».

Подразумевается, что DB уже протестирован до тестирования Users, поэтому в нем нет багов. Можете называть это интеграционным тестированием. Зато оно намного ближе к продакшн-коду по сравнению с мокнутым DB, который может выполнять только заранее заданную последовательность действий.

В моем понимании интеграционное тестирование — это когда тестовое окружение не может быть развернуто и свернуто в рамках одного теста.

2) «Мы можем заменить шаги 1-3 на создание простого мока, реализующего поведение DB».

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

p.s.
У DI*-фреймворков есть еще две проблемы, про которые забыл упомянуть выше:

1) Бесконтрольное разрастание количества зависимостей. При добавлении новой функциональности часто возникает необходимость в дополнительных зависимостях. DI*-фреймворк как бы намекает в этом случае: «не парься и смело добавляй эту гребаную зависимость — я сделаю всю тяжелую работу с инициализацией и передачей зависимости в нужное место за тебя». Это приводит к dependency hell’у c модулями, содержащими 100500 бесполезных зависимостей.

При разработке аналогичной функциональности без DI*-фреймворка программист хорошенько задумается перед добавлением новых зависимостей, если это усложнит код с инициализацией и передачей этих зависимостей. Хороший программист вместо этого отрефакторит код, чтобы уменьшить количество зависимостей. Например, путем их инкапсуляции друг в друга или удалением устаревших зависимостей.

2) Падение производительности приложения. Вся «магия» DI*-фреймворков с инициализацией и передачей зависимостей обычно реализуется с помощью тормозного reflection. Кроме того, DI*-фреймворк может ограничить программиста в переиспользовании уже созданных зависимостей. Вместо этого он будет оказывать медвежью услугу, растрачивая ресурсы CPU и RAM на создание новых экземпляров зависимостей на каждый чих приложения.

3) передаем DB в Users;

Мнэээ... я вообще-то думал, что это «передаём объект» и есть dependency injection в чистом виде. Если Users не создал DB, а получил его готовым (неважно, по ссылке в сам объект, в глобальной переменной, в объекте конфига, или ещё как-то) — это оно и есть. А если ссылка имеет тип «интерфейс DB», а не какой-нибудь объект связи той единственной DB, которая на месте тут — это, очевидно, тот самый DIP и есть. Разве не так?

Шаги 1-2-3 выносим в отдельную функцию, возвращающую объект Users, готовый для тестирования.

Или не сам Users, а какой-нибудь setUp() подготовки окружения — лишь бы в итоге Users знал, где найти DB. Но всё равно это то же самое.

Подразумевается, что DB уже протестирован до тестирования Users, поэтому в нем нет багов. Можете называть это интеграционным тестированием. Зато оно намного ближе к продакшн-коду по сравнению с мокнутым DB, который может выполнять только заранее заданную последовательность действий.

Полноценная версия того же движка DB, даже с ограничениями типа «только в RAM», возможна далеко не всегда. Что будешь делать, если она недоступна?

В моем понимании интеграционное тестирование — это когда тестовое окружение не может быть развернуто и свернуто в рамках одного теста.

«Один тест» может включать в себя создание виртуальной машины с установкой ОС и набивкой пакетами, так что это определение всё равно неоднозначное. Для меня интеграционный тест это функциональный уровня выше одной функции (чтобы его нельзя было назвать юнит-тестом).

У DI*-фреймворков есть еще две проблемы,

Фреймворки это те, что просто облегчают рефакторинг с вынесением такого, или которые строят сами DIP-прослойку на каждую зависимость?

Полноценная версия того же движка DB, даже с ограничениями типа «только в RAM», возможна далеко не всегда. Что будешь делать, если она недоступна?

Использовать полноценную тестовую базу.

Фреймворки это те, что просто облегчают рефакторинг с вынесением такого, или которые строят сами DIP-прослойку на каждую зависимость?

Мне больше попадались фреймворки второго типа, которые «магическим образом» создают объекты зависимостей и передают их туда, где они требуются, согласно предоставленной конфигурации зависимостей.

з цією думкою видно солідарні розробники angular js, де називають передачу кожного параметра dependency injection docs.angularjs.org/tutorial/step_07 :)

Открой доку к любому di контейнеру и на каждый свой коварный вопрос получишь ответ в списке faq
Простите, а зачем тогда здесь эта статья?

затем, что вы не понимаете даже разницы между Dependency Injection и Dependency Inversion Principle

Так вы объяснить всё равно не можете, только кидаетесь на каждого комментатора

Ничего. Ни

разницы между Dependency Injection и Dependency Inversion Principle
Ни что это вообще такое.

Dependency Inversion Principle (инверсия зависимостей) — один из принципов проектирования SOLID.
Dependency Injection (внедрение зависимостей) — паттерн (шаблон) проектирования, построенный в том числе и на базе принципа инверсии зависимостей.
Это две разные вещи. Абсолютно. Одно являтся частным случаем другого. Объяснять обе эти штуки — вагон времени и тонны строк. Про DIP я написала, про DI — и так куча статей, ищите, читайте.

и не благодарите. Любые ответы на ультимативные вопросы.

а вообще — нагуглить можно.

Так зачем тут ваша статья? Это всё тоже можно нагуглить

а зачем вы у меня вопросы спрашиваете в стиле:
— Вы ничего не рассказали про DI! Аааааа!

Я и не собиралась про него рассказывать. Я указала вам, что DIP и DI — разные вещи и вы не там копаете. Зачем так истерить-то? Меня уже типает от этих бесконечных предъяв. Каждому я должна, потому что «ну вы же учитель», «ну вы же написали», «ну зачем же здесь эта статья».

Я искренне благодарна, что вы сделали мне одолжение и прочли материал, который я подготовила. Спасибо, следующая статья будет последней (потому, что она уже написана).

а зачем вы у меня вопросы задаёте в стиле:
Я вообще вопросов не задавал, это был другой человек :)
Зачем так истерить-то? Меня уже типает от этих бесконечных предъяв. Каждому я должна, потому что «ну вы же учитель», «ну вы же написали», «ну зачем же здесь эта статья».
Я не истерю. Мне конечно хотелось бы почитать и про DI, и про DIP простыми и запоминающимися словами, но видимо это невозможно — все пишут или непонятными научными определениями, или кодом.
Я искренне благодарна, что вы сделали мне одолжение и прочли материал, который я подготовила.
Не стоит благодарностей.
Спасибо, следующая статья будет последней (потому, что она уже написана).
Ах, что же мы дальше делать будем?
Ах, что же мы дальше делать будем?
гуглить непонятные научные определения и код.

а, да, было тут мнение, что технического материала не хватает на доу. Ну так вот пусть кто-то другой пишет этот грёбанный материал и вычитывает эти претензии комментаторов. Читайте и дальше обзоры про IT рынок и интервью в стиле «история успеха Васи Пупкина».

Ну так вот пусть кто-то другой пишет этот грёбанный материал и вычитывает эти претензии комментаторов.
Да, кто-то с более крепкими нервами и более спокойным отношением к вопросам.

«Мессия». Это слово пишется так.

исправила. Избранный нас спасёт.

Не хотите на роль Мессии?

Нет — у меня ещё работа есть, а я и так тут много времени провожу.

У Избранного никто не спрашивает, хочет он или нет. Он ДОЛЖЕН! Патамуша он МОЖЕТ. А раз могешь — веди давай нас. Он что, сам себя Избранным назначил что ли? Нет. А раз нет — давай это, отрабатывай тут, веди нас к светлому будущему.

На этот раз Геннадий Догаев. Из всей темы прочёл только заголовок, но советов о том, кому что мне отвечать и как себя вести — написал на уже на целый трактат.

ах, да, про «нагуглить принцип инверсии зависимостей» — удачи.

3 результат в гугле, и даже заключение коротко и понятно написано

When this principle is applied it means the high level classes are not working directly with low level classes, they are using interfaces as an abstract layer. In this case instantiation of new low level objects inside the high level classes(if necessary) can not be done using the operator new. Instead, some of the Creational design patterns can be used, such as Factory Method, Abstract Factory, Prototype.

Вот видите. А вы утверждали, что не умеете гуглить. Стоило чуточку надавить — и пошёл процесс обучения. Не всё ещё потеряно.

Я не утверждал что не умею гуглить. Гуглить я как раз умею, научился этому искусству много лет назад. Но я не гуглю всё подряд каждый день, так как это неэффективно.
А вот почитать статью, которая попадается на доу или в лентах соцсетей, и потом загуглить подробности если интересно — эффективно. Но это не отменяет каких-то требований к статье. Взялись объяснять — объясняйте. Или так и напишите «Пацаны, есть такая классная штука, Dependency Inversion Principle, загуглите».

Правильно. Будем всем сообществом требовать от Васи Пупкина пояснения его секретов успеха. А от сырных дел мастера требовать советов по сочетанию вина и сыра к грядущему ужину. А то чего это они тут пишут на доу. Всецело вас поддерживаю. Надо больше требовать от этих зазнавшихся авторов, а то чего доброго, возомнят о себе невесть чего.

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

Я никому ничем не обязана. По поводу разницы DI и DIP это вы на меня набросились с воплями «а че не обьясняешь!» Там, между прочим, передо мной другой человек пытался донести разницу этих понятий, при чем не первый.
Не нравится как я отвечаю — не надо ко мне обращаться «эй ты, а ну быстро сделала, че я хочу». И вместо того, чтобы отчитывать меня — посмотрите лучше на своё собственное поведение. И вы ещё имеете наглость мне что-то про хамство рассказывать.

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

У вас реально проблемы, обратитесь к специалисту и не пишите нам больше.

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

Не в моём мнении дело. И не в DI и не в DIP дело. Дело в вашем отношении к другим здесь. Оно неадекватно.
Вы создали статью, выбрав заголовок, который обязательно привлечет лишнюю аудиторию. А когда аудитория привлеклась — начали хамить (ну ладно, резко отвечать) на комментарии. Хотя тролли тут ещё даже мимо не пробегали; вы кидаетесь на людей, у которых просто отличное от вашего мнение.

я для вас на форуме специальный топик завела — прошу. dou.ua/...rums/topic/17418/?added=1

не врубаться с десятого раза в то, что DI и DIP — разные вещи — это по вашему «отличное от моего мнение»?

Кидаться на тех, кому что-то непонятно — еще хуже. Это самоутверждение и та самая прокачка ЧСВ, против который вы вроде бы выступали. Вообще складывается впечатление что статью вы писали исключительно с целью показать себя, но нам всем она зачем тогда? Спрашивать нельзя, критиковать нельзя. А это форум, а не авторский блог.

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

Вот мы с вами в формате форума и общаемся. Каким манером вы ко мне обратились — так я с вами и разговариваю. Вас не устраивает, что я не подняла лапки кверху? Ну простите.

давайте я напишу к статье про природу гравитации вопрос о том, «почему на Марсе нет жизни» — вопросы как бы связанные, оба про гравитацию.

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

а ниче, что я не про DI, а про DIP рассказываю, не? Боже....
dou.ua/...version-principle/#919403

Насколько я понимаю, dependency inversion обычно реализуется на основе dependency injection. Т.е. это тесно связанные понятия.
Можете объяснить на пальцах различие между этими понятиями?

С точностью да наоборот, DI частный пример пример использования DIP.

Статья о dependency inversion, в трех словах это проектирование на основе интерфейсов.

Dependency injection это патерн для управления зависимостями в проекте который следует dependency inversion принципу.

Спасибо за объяснения. Ничего не понял. Запутался еще больше. Видно, действительно хорошие штуки эти DI с DIP. Буду использовать их во всех проектах, чтобы не выглядеть дураком в глазах окружающих.

Тут в соседней ветке недавно обсуждали эти классные штуки DI*. С помощью них можно писать вот такой простой и понятный код:

public class MatchContainer implements StatsRegistry, LineupsRegistry, AreaRegistry, UserRegistry, TournamentRegistry, TicketRegistry, TableRegistry, ChatRegistry, BetRegistry, MatchEventRegistry, MatchReactor, ChatReactor, BetReactorThin, AreaReactor, TimeReactor { ... }
public class TournamentCollection implements TournamentRegistry, TicketRegistry, ChatRegistry, BetRegistry, TableRegistry, ChatReactor, BetReactorThin, MatchReactor, TimeReactor { ... }

Там дальше идут пояснения:


Это I из принципов SOLID, который говорит

«Interface segregation principle»: many client-specific interfaces are better than one general-purpose interface.

Видно, SOLID — очень хорошие принципы. Впредь буду всегда их придерживаться.

чем вам SOLID-то не угодили? К чему такие крайности буду всегда / не буду никогда?

SOLID — это cargo cult. Возможно, принципы SOLID могут быть полезны в очень узкой нише. Слепое следование этим принципам на всех подряд проектах обычно ведет к бессмысленному усложнению кода и нагромождению бесполезных абстракций, как показано выше.

Почитайте на досуге www.sandimetz.com/.../20/the-wrong-abstraction и en.wikipedia.org/wiki/KISS_principle .

я вам про крайности вообще-то. Вы мне только что, как я понимаю, тоже про крайности говорите. Тупое следование чему угодно с выключенным могом — всегда плохо. А культ карго — это подражание тому, природу чего не понимаешь. Понимать SOLID надо.

по моєму Александр вже давно продемонстрував розуміння SOLID і DIP і тонкощі роботи з DI контейнерами. Я підписуюсь під кожним його словом.

DIP порождает DI. Честно говоря, меня так задёргали все комментаторы, что я уже ни то что статьи писать не хочу, но и отвечать даже на разумные вопросы. Простите. Если вам интересна тема DI — ищите в других источниках.

Простите, если мой комментарий задел вас. Просто не люблю лишние абстракции и cargo-культ. К сожалению, DI в первую очередь ассоциируется с этими понятиями. DI — хорошая идея для частных случаев. Только не нужно делать из нее карго-культ, пытаясь применить ее везде, где можно и нельзя.

Статья написана классно — простым и понятным языком. Пишите еще.

дело не в вашем конкретно вопросе. Просто я устала. Тем более такой крупный вопрос как DI — в комментах о нём писать нет никакого смысла, надо ваять новую статью — а у меня нет для этого никакого настроения. Может быть когда-нибудь потом.

Статьи написала как раз в таком стиле, чтобы люди использовали все эти «хорошие штуки DI с DIP» как-то более осознанно. DIP является принципом проектирования, на основе которого рождён DI. Без понимания DIP вам будет сложно разобраться с DI. Так что, ищите соответствующий материал с DI и разбирайтесь — должно быть немного попроще.

Наташ, Ви звичайно вибачте, але Олександр видно розуміє DI набагато краще вас, причому на практиці. Дуже легко сказати - ти не розумієш, розбирайся. Використання не на придуманих прикладах DI і побудова слабко-зв'язаного коду, який описує сильно зв'язану бізнес модель призводить до ускладнення коду, слабких абстракцій, неочевидного порядку ініціалізації, якщо Ви використоєте DI Container типу Unity, ви майже відразу впираєтесь в обмеження, і Вам доводиться писати костилі і переносити туди бізнес-логіку. Дебаг перетворюється на пекло. І заради чого це все? Легкість додавання нових залежностей провокує розробників їх додавати. Замість того щоб бачити реальну картинку бізнес-моделі ми замилюємо собі очі. Мок тести які абсолютно відірвані від реальності, і як наслідок тільки заважають. Все це озвучив Олександр, і з усім цим люди стикаються. Але ж ми просто не розуміємо.

вы статью читали вообще? Вы знаете о чём она?

перечитайте мій коментар, і коментарі Олександра вище.

Александр зашёл в тему и с чего-то стал писать про DI. Правильно он пишет или нет — меня вообще не колышет. Статья посвящена другому вопросу — перечитайте её (если вы вообще утруждали себя этим ненужным занятием ранее).
А если вы хотите подискутировать на тему DI — ну так напишите статью (топик) и дискутируйте. Если мне будет интересно — я зайду и поучаствую в вашем диспуте.

Зачем заводить кучу бесполезных интерфейсов ради DI, если за большинством таких интерфейсов на практике редко бывает более одной реализации?

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

Получается вот что (взято из spring, с приветом от DI*):
— SimpleBeanFactoryAwareAspectInstanceFactory
— AbstractSingletonProxyFactoryBean

Источник — www.quora.com/...lass-names-from-real-code

Принципиально не хочу лезть их комментировать, пока не изучил сам в работе. Но отбор по критерию «самые смешные имена» — просто неконструктивен.

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

То, что вы пишите про ООП — это плохо. Не плохо иметь свое представление о принципах ООП, не плохо рассуждать и разбираться. Плохо подавать это как прописную истину.

Я надеюсь, что читатели, которые, находятся на начальном этапе изучения сабжа, обратят внимание на то, что написанное иллюстрируется примерами на PHP и обратятся для изучения ООП к чему-то с менее динамической типизацией.

Лично я в статье и в комментариях увидел жесткую passive-aggressive позицию автора, желание самоутвердиться и сочинение на тему «как я вижу ооп».

Будьте добрее.

ООП ж я к яйця Фаберже. Як повернеш, так і твоє. Не бачу взагалі сенсу доколупуватися до певної реалізації ООП, або казати, що моє ООП більш кошерне ніж твоє ООП.

Ну, отчасти я об этом и пишу. «Это мое видение DIP» vs «Это DIP».

Плюс я бы поспорил с тем, что у каждого свое ООП. Есть основные принципы и шаблоны, которыми люди могут друг с другом коммуницировать. Если вы называете фабрику билдером, а декоратор композитом, то это не ложится в концепцию «у каждого свое ООП». У каждого свои предпочтения и любимые методики. Для некоторых языков существуют более специфические реализации шаблонов. Но кор остается общим — он должен быть таким for people just to stay on the same page.

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

Статья предполагает, что существуют и другие мнения на тему поднятых вопросов, несомненно. Каждый в праве оставаться со своим мнением. Я описала своё видение. Вы можете написать о своём.

Любить или не любить PHP — тоже ваше право.

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

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

И я склонен оперировать фактами. Есть факт — система типов в php. Есть факт — он выполняется динамически. Это имеет свои недостатки, это дает свои преимущества. Моя нелюбовь или любовь к php не имеет никакого отношения к тому, что компайлер не проверяет типы во время компиляции. Т.к. ООП — это один из подходов к построению системы типов, то я указываю на некий диссонанс между инструментом и подходом.

Вы написали обширный пост на темы, не имеющие прямого отношения к статье. У вас фигурируют «мне не нравится» и «не такой код», т.е вы опелируете к вещам, против которых тут же выступаете — к личным предпочтениям. Тем самым, вы ставите меня в позицю, что ваши хотелки важнее моих. Так не будет.

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

Дико извиняюсь, за свою «жесткую passive-aggressive» позицию. Я намерена отстаивать своё мнение не меньше, чем вы своё — это тоже моё право.

1) «апеллируете»
2) то, что вы говорите у меня фигурирует — у меня не фигурирует.
3) я не имею ничего против вашого мнения, имейте и даже рассказывайте всем сколько угодно. Только позиционируйте корректно. Контент — это «DIP. Edition by Наталия Ништа with PHP examples» а не «DIP». Собственно, в этом и заключается цель моих комментариев.

о чём и речь. Замечаний по существу нет.

Наверное, мне рядом с именем ещё надо написать «извиняюсь, что я существую». И что у меня есть собственное мнение — тоже нижайше прошу прощения.

Плюс я бы поспорил с тем, что у каждого свое ООП. Есть основные принципы и шаблоны

как-то противоречит вашему предложению:

DIP. Edition by Наталия Ништа with PHP examples

В чём же тут подвох? Хочет ли человек просто дое*ться или он сам не понимает, что говорит?

У вас фигурируют «мне не нравится» и «не такой код», т.е вы опелируете к вещам, против которых тут же выступаете — к личным предпочтениям. Тем самым, вы ставите меня в позицю, что ваши хотелки важнее моих. Так не будет.

Зря вы не прислушиваетесь и к тому что вам пишут люди. Одно из основных качеств хорошего преподавателя — уменее слышать. До тех пор пока вы будете везде реагировать на то что вам кажеться деструктивной критикой единственным способом — противоставлением, защитой своей точки зрения и отвержением таких слушателей вы врядли сможете добиться ожидаемых результатов в преподавании. Цели должны быть немного другие, способы общения с такой публикой так же — выбранная вами стратегия по психологии не будет работать вам на руку, ведь люди пришедшие к вам на курсы — клиенты, а не призовники в армию.

я не «хороший преподаватель». Я просто пишу о том, о чём считаю нужным. Если человеку не понятно, о чём я говорю — пусть ищет другой материал.

Я не преподаю ни на каких курсах. И слава богу — это же кошмар какой-то. Всем должна.

something is happens -> something is happen or something happens.

Можно на заметку взять. Только там something is happening, наверное.

Это выражение можно сказать требя указанными способами и ещё кучей жаргонизмов. Может, давайте на латыни писать, а?

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

Людина в коректній формі підказує, що варто змінити, аби не муляло око, а ви у відповідь грубіяните.

конкретно ця людина мене вже дістала в інших топіках. Не має аргументів — причепися до правопису. Нічого правити не буду — страждайте.

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

Спасибо за статью, кратко и ясно. А haters gonna hate (:
Ps. С нетерпением жду про принцип Лискоу :)

Мы инвертировали зависимости. До применения принципа DIP у нас присутствовала зависимость Pawnshop от конкретных классов музыкальных инструментов. Теперь же ничто не зависит от конечных реализаций, всё зависит только от абстракций. За исключением наших «изоляторов», куда мы поместили new. Но изменить механизм создания экземпляров внутри этих ограниченных конструкций гораздо легче, чем рыскать по необъятным просторам кода, выискивая, где же мы наплодили наши вновь изменившиеся объекты.
В разрезе статьи было бы оптимально дать пример constructor injection как чистого варианта, при котором класс зависит интерфейсов, и на него не возлагаеться отвественность за свое конструирование и управление жизненным циклом зависимостей.
Вариант со статическим хелпером многие сочтут за антипаттерн, а вы даете его людям, как вполне себе рабочий вариант без каких либо предостирежений.

И еще такой вопрос, зачем так статью называть было?

Мне так захотелось. Аллюзия на знаменитый мем. lurkmore.to/Ломай_меня_полностью

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

Статья уже написана и существует в том виде, в котором существует. Если у вас есть интересные идеи — напишите «открытый ответ» в виде новой статьи или что-то типа того. А в контексте факта, что на доу не хватает технического материала (есть такое мнение) — у вас есть все шансы сорвать овации. Я вас не подкалываю — пишите материал.

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

Большое спасибо! Я как раз и есть тупой «пересичный» читатель :) И мне нравится. Первую статью сразу разобрали с моими 11-классниками (я учитель), кое-что пришлось ещё адаптировать им (уровень общего образования нынче очень не очень... С логикой у детей проблемы большие). Для меня ценна база, подплёка, так сказать, а дальше — уже сам придумываю сказки для разных возрастов. 5-классники — по-смышленей оказались :) Рисуем с ними в Mind Mapping схемы разные. А на суровых дядек-комментаторов не обижайтесь, они на самом деле хорошие и мне тут помогали очень с моими проблемами.

Ни в коем случае ни на кого обижаться не намерена. Но в обиду себя давать не буду. У меня есть ясная позиция и я буду её отстаивать. Как-то я не сильно люблю, когда за мой счёт кто-то прокачивает своё ЧСВ.

Очень рада, что вам пригодились мои мысли по теме уровней абстракции )

Я тоже с критикой:
— почему не взяли UML, а нарисовали что-то обобщенно-запутывающее?
— вы упомянули SOLID и рассказали про один из принципов, и в этот же момент жестко нарушили как минимум один из оставшихся;
— много вопросов по коду — если быть кратким, то приведены примеры, как не стоит писать на php;
— есть вопросы по «логическому до тошноты ООП» — почему вы захардкодили в магазине интерфейс Instrument? а если завтра ломбард начнет продавать оргтехнику? есть вопросы и по дереву наследованию инструментов, и фабрике
— ну и название — новичок, которому интересна тема, никогда не догадается, о чем статья по ее названию, и пройдет мимо

По поводу первого пункта: UML запутает ещё больше. Далеко не все умеют читать такие схемы. Лично я не люблю статьи, для понимания которых нужно половину интеренета перерыть. Автор, конечно, невероятно крут, когда использует «правильные» термины, но не стоит этим злоупотреблять.

По поводу всего остального, в том числе про OCP — я тут вообще-то не крутостью своих проектных решений хвастаться пришла. Но если вам сильно хочется — вы, конечно, можете.
dou.ua/...version-principle/#919374

У вас 5 кружочків, люди які не шарять UML і так зрозуміли бі стрілочки іншого кольору і форми. Тут ж нескладна діаграма.

в «Паттернах проектирования» O’Reilly используют кривые стрелочки «от руки» — и никто не умер.

Я не про крутость, а о том, что прививать правильные вещи нужно комплексно. Single Responsibility намного проще обьяснить, и пользы будет больше. А так вы его нарушили в этой статье, и теперь в потенциальной следующей придется объяснять, почему раньше ему не следовали.
Есть 4 вида кода — сложный+плохой, сложный+хороший, простой+плохой и простой+хороший. Вот эта статья обучает, как перейти от простого+плохого к сложному+плохому.

Я считаю, что в первую очередь нужно следовать принципу разумности. В первом варианте я использую интерфейс, а во втором абстрактный класс — я делаю это намеренно. Я демонстрирую, что можно взять любую из этих конструкций абстракции — и всё получится.

Вы сейчас включили режим «крутой проектировщик» и рассказываете мне, что:

никто не будет писать такого в боевом проекте
Ну как бы да, кэп. Но это не боевой проект, а детальное разжевывание одного из принципов SOLID. Я не совсем понимаю сути ваших претензий.

А в следующей статье таки будет и про OCP. Она уже готова, лежит в редакции, если вам интересно.

Это — не претензии. Это — комментарий к прочитанному.

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

кстати, а где вы тут нарушение SRP углядели? Я вообще-то про ОСР говорила. Расскажите, пожалуйста, где у меня есть класс, в котором нарушен принцип единой ответственности.

За что отвечает класс Instrument? За вопроизведение звуков, (само)починку или (само)упаковку?

он отвечает за действия, производимые над инструментом. Вы ещё ISP туда приплетите. Чтобы для каждого метода — свой интерфейс.

То есть если для починки инструмента понадобятся какие-то материалы, они будут переданы в метод repair? А упаковочная коробка — в метод pack? И выходит, что инструмент знает, как его будут чинить?

А давайте напишем такую систему, в которой, если нам что-то понадобится — оно уже там будет. Вы шутите, да?

У нас есть лаконичное ТЗ — мы ему следуем. Разрастание системы не планируется. Поэтому не надо городить тут звёздные чертоги. «А если» в контексте происходящего не применимо.

Ну и ещё раз повторяю — статья создана для демонстрации DIP. Если вы считаете, что я пишу не о том, не так, не правильно — пишите свой материал так, как сочтёте нужным.

Какая-то слишком бинарная логика — не нравится — пиши свое. А может мне нравится и я хочу улучшить?
Если бы opensource следовал этому принципу, не было бы ни одной нормальной библиотеки или фреймворка — каждый писал бы свой велосипед и закрывал доступ для пул-реквестов.

у вас есть хорошие идеи — давайте напишем что-то в соавторстве.

но раз уж вы подняли тему проектирования. В отрыве от темы статьи. С вашим условием выполнение ремонта и упаковки вполне можно делегировать из Instrument специальным классам. Композиция, все дела. Но я это вижу как абстрактный класс, а не интерфейс.

И передать в специальные классы $this? Я правильно понял?

class Instrument {

    private $repairLaboratory;

    public function repair() {
        $this->repairLaboratory->fix($this);    
    }
}

нафига? Пока абстракция простая — нет вообще необходимости куда-то что-то делегировать. Как только у нас появляются подгруппы Instrument — можно для них создать свои абстракции, наследующие от Instrument, в каждой из них делегировать в класс, отвечающий за свою группу инструментов.

Я что-то вообще потерялся. Ладно, давайте закроем это дерево комментов.

Далеко не все умеют читать такие схемы.
ЦА статьи — разработчики, как я понимаю. Они обычно умеют в UML.

Простые вещи из UML интуитивно понимаемы.

на следующей неделе последнюю серию выпустят )

Спасибо! Стало более понятно)

Прочитал, покивал, ничего нового. Пользоваться этой вещью приходится, т.к. любая нормальная работа к этому обязывает. Либо какой-нибудь симфони2, зенд2, когда стараешься писать так, как в сорцах фреймворка, либо банально разработанная фича в лоб не проходит ревью либо при покрытии тестами придется делать нормально.

И вот об этом

Это и значит проектировать на уровне интерфейсов, а не реализаций.
ни слова.
А это именно та часть, которую многие не понимают.
Для чего интерфейсы? Что это? Когда они появляются? Какой в них смысл? Зачем и почему?
Возможны-ли интерфейсы в языках программирования без явного ООП?
Дальше, ты ввела с бухты барахты «фабрику». Что это? Зачем, почему и так далее.
Пока же статейка ни о чем. Ну и по статейке похоже, что ты и сама в этом еще не разобралась.
Возможны-ли интерфейсы в языках программирования без явного ООП?
Дуже цікаве запитання. Ще й як. Бо інтерфейс — це підхід до побудови абстракції :D
Будував інтерфейси на рівнях “система-система”, “прошарок системи-прошарок системи”, “модуль-модуль”, і навіть ще дрібніше. ООП інтерфейси — це маленький дрібненький варіант реалізації загального паттерна “Інтерфейс”.

Выше были вопросы к ТС, а не вообще. И по идее она должна была раскрыть их. Привести примеры, в том числе, и из не чистого программинга.
А тот же ООП можно использовать даже, если пишешь на асме, не говоря уже о таком понятии, как интерфейс.

Не тупий, розумію, що запитання були автору статті. ;) Просто цікаву тему підняли

Что в ней тикавого?
Важней вопрос, что большинство начинают разбираться в этом вопросе не стой стороны. Имхо, надо стартовать от понятия «абстракция» и после вводить понятие «интерфейс». И то и другое применимы почти во всех областях жизни. Например автомобиль и интерфейс для его заправки.
А затем уже спускаться к программированию.
Или вот взять Лабвью, Симулинк, Скратч — там всё визуально видно. А затем переходить уже к текстовым программам.
И более того, понимать почему одни части кода называют глаголами, а другие существительными.

вы вообще читали, о чём тут написано? По абстракциям отдельная статья — и она первая. А эта статья — к ней вдогонку. В первом же абзаце написано:

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

Коммент из серии «я не читал, но осуждаю».

там вообще про пирожки.

кому пирожки, кому абстракции.

Спасибо, давно не было технических статей!

ну не всё ж про платья и сыры писать.

А вот про сыры, кстати, ничего нет интересного?

Не имею должной компетенции, чтобы давать рекомендации в данном вопросе. Мне нравится Маасдам и Дорблю. Всё остальное — от лукавого.

хорошая статья от специалиста. А я в этом вопросе — диллетант со своими диллетантскими вкусами

Привет. Простите, я с критикой.

Зачем тут растровые скриншоты? У вас интерфейс Instrument и абстрактный класс Instrument, зачем такая путаница? Почему вы сравниваете три реализации метода prepareToSell() с тремя разными сигнатурами?

И самое главное: с моей субъективной точки зрения, сама тема DI не очень-то раскрыта, профит от использования DI недостаточно ясен. Сравните с известной статьёй Фабиена: он подробно перечисляет, чем плохи варианты с жёсткими зависимостями.

В остальном же я приветствую подобные статьи на DOU. Спасибо вам.

DI — Dependency Injection
DIP — Dependency inversion principle

Статья про DIP, моей целью не было просвящать на тему DI.

Зачем тут растровые скриншоты?
Потому что я присылаю статью в готовом виде. Я не подготавливаю её к публикации. Я понятия не имею, как на доу будут выглядеть куски кода в тегах code. Поэтому я присылаю его в виде картинок, а редактор доу (это человек) их вставляет в текст статьи.

Дело привычки, конечно, но, по моим наблюдениям, большинство используют белый а не черный фон в IDE да и сайт с белыми страницами. Но это так, не существенно. Не обычно читать.

у меня наблюдения диаметрально противоположные ) Большинство моих друзей используют тёмный фон в IDE — так глаза меньше устают.

я думал, что с чёрным фоном у IDE работать лучше только ночью.. но наверное кому как :)
а за статью спасибо !

Я пробовал и темный и светлый. В общем пофиг, светлый легче воспринимается, если яркость монитора не завышать.
Но в статье выше темный мешает читать написанное там.

Тоже дело привычки, я работаю с темным и в статье не заметил проблем с прочтением.

А меня эти картинки заставили всматриваться, что там накорябано. Особенно фиолетовый на черном.

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

предыдущая была необходима. Она — база. Следующая статья ещё тяжелее. От простого к сложному — иначе непонятно ничего.

так предыдущая была сложнее этой))

я поняла ) Вы про стиль изложения. А я про тяжесть материала.

в предыдущей — чистая философия. Я не вижу возможности, как её объяснить с «примерами кода». Тема абстракций слишком абстрактна )

согласен) но я не про примеры кода, а вообще про примеры. Довольно сумбурно, скомканно, и в итоге — непонятно получилось. О чем я и прокомментировал)
Надеюсь, следующая будет по доступности >= этой, желаю удачи)

ну, вы можете исправить ситуацию и написать про абстракции свою версию ) Мне очень интересно, как это можно сделать стройно и «по линеечке», чтобы было понятно всем с первого раза. Очень прошу.

спасибо, мне не достает синьйорности для того, что б капитанить про азы) и на «слабо» я не очень берусь) или же это отсылка на «сперва добейся»?

это морской закон: «Кто предложил, тот и делает».

мы ж не на море)))
не нужно быть поваром, чтобы оценить блюдо

а можете мне скапитанить, где в сети или, может, в какой книге я могу доходчиво прочесть про уровни абстракций?

извините, что влажу, но могу посоветовать Стив Макконнелл — «Совершенный код»
очень интересно
«Абстракция — один из главных способов борьбы со сложностью реального мира.» ©

Макконнелл пишет про абстрактные типы данных.

сорри, значит я Вас не понял

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

Я наверное слишком стар, что еще помню: Дейкстра и Майерс отвечают на эти вопросы )

и вот мне человек пишет, что я «капитаню» на счет этих гребанных уровней абстракций недостаточно вразумительно. Ему, видите ли, кажется «сумбурно, скомканно» — ну так пусть сам пишет, раз такой уиный. Или пусть ткнёт, где мне читать.

что Вы так сагрились на слово капитанить?)
еще раз повторю, «сперва добейся» — это не аргумент.
1. Немного мисандестендинг, да, я был неправ насчет «уровней абстракций», почему-то отложилось, что статья про само понятие «абстракция». (Что тоже неплохо характеризует доходчивость статьи)) ну или меня, как читателя)
2. Да, погуглив, внятной статьи не нашел. Что нисколько не указывает на качество Вашей статьи, равно как и не побуждает пейсать свою.
З.Ы. Вот вам новичек скажет ниче не понял, тоже скажете «пиши свою»?) божественное обучение

у меня для таких случаев есть специальная картинка: pp.vk.me/...564/1bb9f/ebW_QAP6_38.jpg

Хорошая картинка. Можно было бы её вместо статьи поместить.

Конечно можно. Персонально для вас. Мне никто эти темы не обьяснял.

Ну, вам никто не обьяснил, вы разобрались сами — молодец, чего уж. Так об чем статья-то — вы просто хвастаетесь, или все-таки передать знания хотите?

Я не понимаю, чего вы от меня хотите. У вас нет конкретных вопросов. Только общие претензии «непонятно». Я как могла — изложила в статье. По-другому я не умею. Если вы умеете и знаете как сделать лучше — что вы от меня-то хотите?

Наличие возможности комментирования подразумевает некий фидбек — который может быть как в виде конкретных вопросов, так и оценки опуса в целом. Пусть даже это и, возможно, ранит творческую душу автора.
P.S. Лично я от вас вообще ничего не хочу.

Ниче не понятно. Нас обманули — расходимся.

Да вы уже разошлись в комментариях.

И снова повторяюсь: прежде чем давать советы — попробуйте сначала своим советам следовать сами.

Лечите самооценку

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

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

Дорогая Наталья! Когда я прочитал Вашу первую статью, я по ссылкам перешёл на целый цикл статей habrahabr.ru/users/maxstroy/topics, где прочитал уже 4 поста и продолжу дальше. Человек там развивает тему абстракций и их нотаций в разных парадигмах. Это сподвигло меня ещё раз окунуться в первоисточники (Готлоб Фреге, Куайн, Джон Сёрл и основателей аналитической философии). То есть, благодаря Вам, я целый увлекательный курс прошёл и продолжаю. В новой Вашей статье на уровне модальной логики Вы замечательно донесли свою мысль. Примеры в коде демонстрируют именно ход мысли, детали не так важны (PHP мне не родной, но это и не важно). Так что всё замечательно и мне обидно, что Вы так болезненно реагируете на чьи-то замечания. Пишите ради меня и моих детей, их у меня 256 :) и нам очень полезно и интересно. И да, если б я реагировал на все гадости, что коллеги про меня говорят, то уже бы давно был «на дурке». Спасибо!

Я холерик, с этим ничего не поделаешь :-) Кроме того, я достаточно открытый человек и стараюсь отвечать на вопросы, которые мне задают — только вот не всегда понятно, зачем человек пришел: от души спросить или просто поржать/поглумиться и т.д. Этот материал — серьезный, хоть и написан в стиле комедийного шоу. Поэтому и отвечаю я на него со всей серьезностью — для меня это важно. Через пару дней я наемся этих комментариев, мне станет скучно и я переключу свою энергию в другое русло. Все идёт по обычному сценарию :-)

Статья ещё должна быть одна, она уже написана — о том, как я вижу попринцип подстановки Барбары Лисков. В ближайших планах писать на тему ООП больше не планирую. Разве что про композицию, может быть напишу позже, когда будет вдохновение.

Преклоняюсь перед вашим трудом. Учить такую ораву прыгающих / ногами дрыгающих — я бы не смогла.

Пишите ради меня и моих детей, их у меня 256
На следующей итерации планируется увеличить до 512? ))

нууу, да. Переход на личности это вообще серебрянная пуля в любом диалоге)
Отвечать подобным образом не буду, по комментариям и так все видно
Пожалуй, только повторю совет: относитесь проще к критике и людям
всего хорошего)

плюс предыдущая совсем для начинающих. А DIP не могут объяснить даже некоторые солидные программисты со стажем.

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

возможно. Но по собственному опыту могу сказать, что мне бы пригодился этот материал, когда я начинала разбираться с ООП. Ну и кроме того, не напиши я там, что статья «для начинающих» — сообщество поджарило бы меня на вертеле без права помилования. «Ведь все знают, что такое Абстракция». Рада, что вы увидели суть.

«у нас куча 23-летних синьоров и все равно х*р его знает, чем абстрактный класс отличается от интерфейса» ©

а полный текст помнишь? не помню где я это читал, прикольно

джависты объясняют очень просто. ДИП это аутовайринг в Спринге , и точка :-)

Спасибо, приятно читать програмерскую статью. Мне кажется, жестко захардкоджено перечень операций перед продажей. Что, если части инструментов не надо ремонт а другой части не надо упаковка? Вынести это на уровень фабрики factoty::getPreparedInstrument();

стоп-стоп-стоп. Мы тут не проектированием занимаемся для конкретной задачи, а разбираем «как не надо делать и почему». Думаете, если я сразу напишу «правильную» конструкцию — кто-то что-то поймёт по теме DIP?

спасибо за статью, просто и понятно :)

Подписаться на комментарии