Почему контейнеры IOC не нужны с динамическими языками - PullRequest
41 голосов
/ 16 февраля 2010

Кто-то из подкаста Herding Code № 68, http://herdingcode.com/herding-code-68-new-year-shenanigans/, заявил, что в контейнерах IOC нет места для Python или Javascript или слов для этого. Я предполагаю, что это общепринятая мудрость и что она применима ко всем динамическим языкам. Зачем? Что в динамических языках делает ненужными контейнеры IOC?

Ответы [ 9 ]

46 голосов
/ 22 февраля 2010

IoC предоставляет механизм для разрыва связи, которую вы получаете, когда объект вызывает 'new' в другом классе. Эта связь связывает вызывающий объект с реализованной реализацией любого интерфейса, который он реализует.

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

В динамических языках вызов new X является заполнителем для "создания экземпляра любого класса, определенного как X в точке выполнения". Это более слабая связь , так как она связана только с именем X.

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

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

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

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

Как сказал Йорг В. Миттаг .. «Эти инструменты не нужны, принципы проектирования - нет». Я считаю, что они не нужны, но сделаны правильно, все еще ценны.

24 голосов
/ 22 февраля 2010

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

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

Контейнер IOC - это просто инструмент для управления этой организацией.

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

20 голосов
/ 08 декабря 2011

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

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

Если у вас есть логический блок X, который имеет известные взаимодействия с логическим блоком Y, вы можете создать MockY с предопределенным поведением и явно проверить логику X.

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

Должно быть два вида тестов:

  • Модульные тесты, которые выполняются в ЛЮБОЙ среде и проверяют логику отдельных единиц кода.
  • Интеграционные / функциональные тесты, которые проверяют объединенную логику приложения.

Теперь к вопросу: IoC. Для чего нужен IoC? Это удобно для нескольких вещей, но действительно очень хорошо для , облегчая использование внедрения зависимостей :

// Do this every time you want an instance of myServiceType
var SystemA = new SystemA()
var SystemB = new SystemB()
var SystemC = new SystemC(SystemA, "OtherThing")
var SystemD = new SystemD(SystemB, SystemC)
var IRepo = new MySqlRepo()
var myService = new myServiceType(SystemD, IRepo)

В эту логику:

// Do this at application start
Container.Register(ISystemA, SystemA)
Container.Register(ISystemB, SystemB)
Container.Register(ISystemC, SystemC)
Container.Register(ISystemD, SystemD)
Container.Register(IRepo, MySqlRepo)
Container.Register(myServiceType)

// Do this any time you like
var myService = Container.resolve(myServiceType)

Теперь, почему мы не видим МОК во многих динамических языках?

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

... и это было бы потому, что обычно выполняемое в них тестирование не существует.

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

Это все чепуха.

Для проекта без юнит-тестов или юнит-тестов с плохим охватом кода нет оправдания .

... но поразительно количество javascript и python проектов, которые я видел (выбирая эти два именно потому, что они представляют интерес, и я видел больше проектов такого типа, чем другие) без IoC, без DI и, что неудивительно, без тестов.

Здесь есть отличная статья о DI на сайте: http://code.google.com/p/google-guice/wiki/Motivation

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

Резюме:

  • IoC полезен для вещей, но в первую очередь для реализации DI
  • IoC - это НЕ XML-файлы конфигурации. > _ <</li>
  • DI полезен для тестов
  • Отсутствие МОК свидетельствует об отсутствии DI, что свидетельствует об отсутствии хорошего тестирования.
  • Использовать IoC.
16 голосов
/ 16 февраля 2010

Потому что они уже встроены в язык.

Контейнер IoC предоставляет две вещи:

  • динамическое связывание
  • динамический язык (обычно невероятно дурацкий, построенный поверх XML или в более новых версиях поверх аннотаций Java / атрибутов .NET)

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

Другой способ взглянуть на это: что позволяет вам делать контейнер IoC? Это позволяет вам брать независимые компоненты и соединять их вместе в приложение, при этом ни один из компонентов ничего не знает друг о друге. Существует название для объединения независимых частей в приложение: сценарии! (Это в значительной степени определение сценариев.) Многие динамические языки также довольно хороши в сценариях, поэтому они идеально подходят в качестве контейнеров IoC.

Обратите внимание, что я не говорю о внедрении зависимости или инверсии контроля. DI и IoC просто так же важны в динамических языках, как и в статических языках, по тем же причинам. Я говорю о контейнерах IoC и платформах DI. Эти инструменты не нужны, принципы не нужны.

4 голосов
/ 13 марта 2016

Я считаю, что контейнеры IoC необходимы в больших приложениях JavaScript. Вы можете видеть, что некоторые популярные JavaScript-фреймворки содержат контейнер IoC (например, Angular $injector).

Я разработал контейнер IoC под названием InversifyJS, вы можете узнать о нем больше на http://inversify.io/.

Некоторые JavaScript-контейнеры IoC объявляют зависимости для внедрения следующим образом:

import Katana from "./entitites/katana";
import Shuriken from "./entitites/shuriken";

@inject(Katana, Shuriken) // Wrong as Ninja is aware of Katana and Shuriken!
class Ninja {
  constructor(katana: IKatana, shuriken: IShuriken) {
    // ...

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

InversifyJS предлагает вам настоящую развязку. Файл ninja.js никогда не будет указывать на файлы катаны или сюрикена. Однако он будет указывать на интерфейсы (во время разработки) или строковые литералы (во время выполнения), что допустимо, потому что это абстракции и в зависимости от абстракций - это то, чем является DI все о.

import * as TYPES from "./constants/types";

@inject(TYPES.IKATANA, TYPES.ISHURIKEN) // Right as Ninja is aware of abstractions of Katana and Shuriken!
class Ninja { 
  constructor(katana: IKatana, shuriken: IShuriken) {
    // ...

Ядро InversifyJS - единственный элемент в приложении, осведомленный о жизненном цикле и зависимостях. Мы рекомендуем сделать это в файле с именем inversify.config.ts и сохранить его в корневой папке, содержащей исходный код приложения:

import * as TYPES from "./constants/types";
import Katana from "./entitites/katana";
import Shuriken from "./entitites/shuriken";
import Ninja from "./entitites/ninja";

kernel.bind<IKatana>(TYPES.IKATANA).to(Katana);
kernel.bind<IShuriken>(TYPES.ISHURIKEN).to(Shuriken);
kernel.bind<INinja>(TYPES.ININJA).to(Ninja);

Это означает, что все соединения в вашем приложении происходят в одном уникальном месте : файл inversify.config.ts. Это действительно важно, и мы собираемся доказать это на примере. Давайте представим, что мы меняем сложность в игре. Нам просто нужно перейти на inversify.config.ts и изменить привязку Katana:

import Katana from "./entitites/SharpKatana";

if(difficulty === "hard") {
    kernel.bind<IKatana>(TYPES.IKATANA).to(SharpKatana);
} else {
    kernel.bind<IKatana>(TYPES.IKATANA).to(Katana);
}

Вам не нужно изменять файл ниндзя!

Цена - это строковые литералы, но эта цена может быть снижена, если вы объявите все свои строковые литералы в файле, который содержит константы ( как действия в Redux ). Хорошей новостью является то, что в будущем строковые литералы могут в конечном итоге генерироваться компилятором TS , но на данный момент это находится в распоряжении комитета TC39.

Вы можете попробовать это онлайн здесь .

4 голосов
/ 26 июня 2011

IoC предоставляет механизм для разрыва связи, которую вы получаете, когда объект вызывает 'new' в другом классе.

Это наивный взгляд на IoC. Обычно IoC также решает:

  • разрешение зависимостей
  • автоматический поиск и инициализация компонентов (если вы используете 'require' с IoC, что-то не так)
  • работает не только с синглетами, но и с динамической областью действия
  • 99,9% времени это невидимо для разработчика
  • устраняет необходимость в app.config

полная статья Вы недооцениваете силу IoC

3 голосов
/ 16 февраля 2010

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

2 голосов
/ 22 февраля 2010

Контейнеры IoC действительно позволяют создавать композиционный слой на статически типизированных процедурных / OO-языках.

Этот композиционный слой существует относительно естественно в динамических языках, таких как Python или Javascript (учтите, что Javascript в значительной степени основан на Scheme).

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

0 голосов
/ 07 июня 2010

Код стада 82 (6/6/10) сравнивает Ruby с .NET и включает в себя некоторые подробные обсуждения того, в какой степени .NET требуется больше IOC / DI, чем Ruby.

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