Контейнеры МОК нарушают принципы ООП


51

Какова цель контейнеров МОК? Объединенные причины этого могут быть упрощены до следующего:

При использовании принципов разработки OOP / SOLID Dependency Injection становится беспорядочным. Либо у вас есть точки входа верхнего уровня, управляющие зависимостями для нескольких уровней ниже их самих и рекурсивно пропускающие зависимости через конструкцию, либо у вас есть несколько дублирующих код в шаблонах фабрики / сборщика и интерфейсах, которые строят зависимости по мере необходимости.

Не существует ООП / ТВЕРДЫХ способов сделать это И иметь очень красивый код.

Если это предыдущее утверждение верно, то как Контейнеры IOC делают это? Насколько я знаю, они не используют какой-то неизвестный метод, который нельзя сделать с помощью ручного DI. Поэтому единственное объяснение состоит в том, что контейнеры IOC нарушают принципы OOP / SOLID, используя частные средства доступа к статическим объектам.

Контейнеры IOC нарушают следующие принципы за кулисами? Это реальный вопрос, так как у меня хорошее понимание, но есть ощущение, что кто-то другой понимает лучше:

  1. Контроль области . Scope - это причина почти каждого решения, которое я принимаю в отношении дизайна кода. Блок, Локальный, Модуль, Статический / Глобальный. Область действия должна быть очень четкой, как можно больше на уровне блоков и как можно меньше в Static. Вы должны увидеть объявления, экземпляры и окончания жизненного цикла. Я доверяю языку и GC, чтобы управлять областью, пока я ясно с ним. В своем исследовании я обнаружил, что контейнеры IOC устанавливают большинство или все зависимости как статические и контролируют их через некоторую реализацию AOP за кулисами. Так что ничего не прозрачно.
  2. Инкапсуляция . Какова цель инкапсуляции? Почему мы должны держать частных членов так? По практическим соображениям разработчики нашего API не могут нарушить реализацию, изменив состояние (которым должен управлять класс владельца). Но также, по соображениям безопасности, инъекции не могут происходить так, чтобы обгонять наши государства-члены и обходить валидацию и контроль классов. Так что все (платформы Mocking или IOC), которые каким-то образом внедряют код перед компиляцией, чтобы предоставить внешний доступ частным членам, довольно огромно.
  3. Принцип единой ответственности . На первый взгляд, Контейнеры МОК, кажется, делают вещи чище. Но представьте, как бы вы сделали то же самое без вспомогательных структур. У вас будут конструкторы с дюжиной зависимостей, которые будут переданы. Это не значит, что нужно закрывать их контейнерами IOC, это хорошо! Это знак перефакторинга вашего кода и следования SRP.
  4. Открыто / Закрыто . Точно так же, как SRP не только для класса (я применяю SRP к линиям с одной ответственностью, не говоря уже о методах). Open / Closed - это не просто теория высокого уровня, чтобы не изменять код класса. Это практика понимания конфигурации вашей программы и контроля над тем, что изменяется и что расширяется. Контейнеры IOC могут частично изменить работу ваших классов, потому что:

    • а. Основной код не определяет распределение зависимостей, а конфигурация фреймворка.

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

Таким образом, конфигурация класса на самом деле не закрыта, она меняется, основываясь на конфигурации стороннего инструмента.

Причина в том, что это вопрос, потому что я не обязательно являюсь мастером всех контейнеров МОК. И хотя идея контейнера IOC хороша, они кажутся просто фасадом, который скрывает плохую реализацию. Но чтобы выполнить внешний контроль Scope, доступ к приватному члену и отложенную загрузку, необходимо выполнить много нетривиальных сомнительных вещей. AOP великолепен, но способ, которым это достигается через контейнеры IOC, также сомнителен.

Я могу доверять C # и .NET GC, чтобы делать то, что я ожидаю. Но я не могу доверить то же самое стороннему инструменту, который изменяет мой скомпилированный код для выполнения обходных решений, которые я не смог бы сделать вручную.

Например: Entity Framework и другие ORM создают строго типизированные объекты и сопоставляют их с объектами базы данных, а также предоставляют базовые функциональные возможности для выполнения CRUD. Любой желающий может создать свой собственный ORM и продолжать следовать принципам OOP / SOLID вручную. Но эти рамки помогают нам, поэтому нам не нужно каждый раз заново изобретать колесо. В то время как контейнеры IOC, похоже, помогают нам целенаправленно работать над принципами ООП / ТВЕРДЫЕ и скрывать это.


13
+1 Корреляция между IOCи Explicitness of codeэто именно то, с чем у меня проблемы. Ручной DI может легко стать рутиной, но он по крайней мере помещает самодостаточный явный поток программ в одно место - «Вы получаете то, что видите, вы видите то, что получаете». В то время как привязки и объявления контейнеров МОК могут легко стать скрытой параллельной программой.
Евгений Подскал,

6
Как это вообще вопрос?
Стивен

8
Это больше похоже на пост в блоге, чем на вопрос.
Барт ван Инген Шенау

4
У меня есть внутренняя категория для вопросов, «слишком аргументированная», которая часто не является формально близкой причиной, но я голосую, а иногда и голосую, чтобы закрыть «не реальный вопрос». У этого есть два аспекта, и этот вопрос встречает только первый. (1) Вопрос формулирует тезис и спрашивает, является ли он правильным; (2) спрашивающий отвечает на несогласные ответы, отстаивая исходную позицию. Ответ Мэтью противоречит некоторым из того, что говорится в вопросе, поэтому, если спрашивающий не ползет повсюду, я лично классифицирую это как добросовестный вопрос, хотя и в форме «Я прав, не так ли?»
Стив Джессоп

3
@BenAaronson Спасибо, Бен. Я пытаюсь рассказать о том, что я узнал в ходе своих исследований, и получить ответы о природе контейнеров МОК. В результате моего исследования я обнаружил, что эти принципы нарушены. Лучший ответ на мой вопрос - подтвердить или опровергнуть, что Контейнеры IOC могут каким-то образом делать то, что мы не можем делать вручную, не нарушая Принципы OOP / SOLID. Из-за ваших замечательных комментариев я реорганизовал свой вопрос с большим количеством примеров.
Суамере

Ответы:


35

Я расскажу о ваших пунктах численно, но, во-первых, есть кое-что, к чему вы должны быть очень осторожны: не путайте, как потребитель использует библиотеку с тем, как она реализована . Хорошими примерами этого являются Entity Framework (который вы сами цитируете как хорошую библиотеку) и MVC ASP.NET. Оба из них делают очень многое под капотом, например, с отражением, которое абсолютно не будет считаться хорошим дизайном, если вы распространяете его с помощью повседневного кода.

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

Итак, чтобы пройти через точку:

  1. Сразу же мы достигаем того, что я могу только предположить, является примером выше. Вы говорите, что контейнеры IOC устанавливают большинство зависимостей как статические. Что ж, возможно, некоторые подробности реализации того, как они работают, включают статическое хранилище (хотя, учитывая, что они, как правило, имеют в IKernelкачестве основного хранилища экземпляр объекта типа Ninject , я даже сомневаюсь в этом). Но это не ваша забота!

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

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

    А как насчет контейнера IoC? Ну, вы делаете точно так же! Опять же , идя по терминологии Ninject, вы посмотрите на то, где связывание установлено, и найти , говорит ли это что - то вроде InTransientScope, InSingletonScopeили что угодно. Во всяком случае, это может быть более понятным, потому что у вас есть код, объявляющий его область действия, вместо того, чтобы искать метод для отслеживания того, что происходит с каким-либо объектом (объект может быть ограничен до блока, но используется ли он несколько раз в этом например, блок или только один раз). Поэтому, возможно, вас отталкивает мысль о необходимости использовать функцию в контейнере IoC, а не функцию необработанного языка, чтобы определять область действия, но если вы доверяете своей библиотеке IoC, что и должно быть! - в этом нет реального недостатка ,

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

    Или, может быть, они предоставляют такую ​​возможность, как инъекция в частные сеттеры? Честно говоря, я никогда не сталкивался с этим, и я сомневаюсь, является ли это общей чертой. Но даже если он есть, это простой случай использования инструмента, который может быть использован не по назначению. Помните, что даже без контейнера IoC, это всего лишь несколько строк кода Reflection для доступа и изменения частного свойства. Это то, что вы почти никогда не должны делать, но это не значит, что .NET плохо раскрывает возможности. Если кто-то так явно и дико злоупотребляет инструментом, это вина человека, а не инструмента.

  3. Конечная точка здесь аналогична 2. В подавляющем большинстве случаев контейнеры IoC действительно используют конструктор! Внедрение сеттера предлагается для очень специфических обстоятельств, когда по определенным причинам инъекция конструктора не может быть использована. Любой, кто все время использует инъекцию сеттера, чтобы скрыть, сколько зависимостей передается, пользуется этим инструментом. Это не вина инструмента, это их.

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

    Это особенно верно, потому что это также то, что вы можете сделать так же легко с DI бедного человека:

    var myObject 
       = new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
    

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

  4. Изменение того, как ваши классы работают вместе, не является нарушением OCP. Если это так, то любая инверсия зависимостей будет поощрять нарушение OCP. Если бы это было так, они не были бы в одном и том же сплошном сокращении!

    Более того, ни пункты а), ни б) не приблизились к тому, чтобы иметь какое-либо отношение к OCP. Я даже не знаю, как ответить на них в отношении OCP.

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

И последнее, что вы скажете:

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

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

добавление

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

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

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

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

void Bind(Type interfaceType, Type concreteType, bool singleton);
T Resolve<T>();

Bindговорит "где вы видите аргумент конструктора типа interfaceType, передавайте экземпляр concreteType". Дополнительный singletonпараметр указывает, использовать ли один и тот же экземпляр concreteTypeкаждый раз или всегда создавать новый.

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

Вы можете попробовать реализовать это самостоятельно, и вы обнаружите, что вам нужно немного поразмышлять и немного кешировать привязки, где singletonэто правда, но, конечно, ничего радикального или ужасающего. И как только вы закончите - вуаля , у вас будет ядро ​​вашего собственного контейнера IoC! Это действительно так страшно? Единственная реальная разница между этим и Ninject, StructureMap, Castle Windsor или любым другим, который вы предпочитаете, заключается в том, что они обладают гораздо большей функциональностью, чтобы покрыть (многие!) Случаи использования, когда этой базовой версии было бы недостаточно. Но в основе того, что у вас есть, лежит сущность контейнера IoC.


Спасибо Бен. Честно говоря, я работал с IOC Containers только около года, и, хотя я этого не делаю, учебные пособия и коллеги вокруг меня действительно сосредоточены на внедрении свойств (в том числе частного). Это безумие, и оно затрагивает все мои замечания. Но даже если другие находятся в такой ситуации, я думаю, что лучшее, что они могут сделать, это узнать больше о том, как должны работать контейнеры IOC, чтобы следовать принципам, и указать на этих нарушителей принципов в обзорах кода. Однако ... (продолжение)
Суамер,

Мое самое большое беспокойство не обязательно перечислено в Принципах ООП / ТВЕРДОГО, и это - явная область видимости. Я по-прежнему выбрасываю область видимости ключевого, локального, модульного и глобального уровней в окно для ключевого слова. Сама идея использования блоков и объявлений, определяющих, когда что-то выходит из области видимости или удаляется, огромна для меня, и я думаю, что это была самая страшная часть IOC Containers для меня, чтобы заменить этот самый базовый элемент разработки на ключевое слово расширения ,
Суамер,

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

2
Что касается пункта 4, то внедрение зависимостей не входит в аббревиатуру SOLID . «D» = «Принцип инверсии зависимости», который является совершенно не связанным понятием.
астрельцов

@astr Хорошая мысль. Я заменил «впрыск» на «инверсию», так как думаю, что именно это я и хотел написать в первую очередь. Это все еще немного упрощает, поскольку инверсия зависимостей не обязательно подразумевает несколько конкретных реализаций, но я думаю, что смысл достаточно ясен. Было бы опасно зависеть от полиморфной абстракции, если переключение реализаций было нарушением OCP.
Бен Ааронсон

27

Разве контейнеры МОК не нарушают многие из этих принципов?

В теории? Нет. Контейнеры IoC - это всего лишь инструмент. У вас могут быть объекты с единственной ответственностью, хорошо разделенные интерфейсы, изменение их поведения после записи и т. Д.

На практике? Абсолютно.

Я имею в виду, что я слышал, как немногие программисты говорят, что они используют контейнеры IoC специально, чтобы позволить вам использовать некоторую конфигурацию для изменения поведения системы - нарушая принцип Open / Closed. Раньше были шутки о кодировании в XML, потому что 90% их работы заключались в исправлении конфигурации Spring. И вы, безусловно, правы, что сокрытие зависимостей (что, правда, делают не все контейнеры IoC) - это быстрый и простой способ создания высокосвязанного и очень хрупкого кода.

Давайте посмотрим на это по-другому. Давайте рассмотрим очень простой класс, который имеет одну базовую зависимость. Это зависит от Actionделегата или функтора, который не принимает параметров и возвращает void. Какова ответственность этого класса? Ты не можешь сказать. Я мог бы назвать эту зависимость чем-то таким, CleanUpActionчто заставляет вас думать, что она делает то, что вы хотите, чтобы она делала. Как только IoC вступает в игру, он становится чем угодно . Любой может зайти в конфигурацию и изменить свое программное обеспечение, чтобы сделать довольно много произвольных вещей.

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

«Но это фиктивный пример! Мои вещи не позволяют менять поведение!» Вы восклицаете. Извините, что сказал вам, но это так. Если интерфейс, который вы вводите, это просто данные. И ваш интерфейс - это не просто данные, потому что, если бы это были просто данные, вы бы строили простой объект и использовали его. Как только у вас есть виртуальный метод в точке ввода, контракт вашего класса с использованием IoC будет «Я могу сделать все, что угодно ».

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

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


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

12
Но IoC прямо противоположен тому, что вы сказали. Он использует открытость / закрытость программы. Если вы можете изменить поведение всей программы без необходимости изменения самого кода.
Эйфорическое

6
@Euphoric - открыт для расширения, закрыт для модификации. Если вы можете изменить поведение всей программы, она не закрыта для модификации, не так ли? Конфигурация IoC по-прежнему является вашим программным обеспечением, это все еще код, используемый вашими классами для выполнения работы, даже если он в XML, а не в Java или C # или чем-то еще
Теластин

4
@Telastyn «Закрыто для модификации» означает, что нет необходимости изменять (модифицировать) код, изменять поведение целого. С любой внешней конфигурацией вы можете просто указать ей новую DLL и вести себя как целое изменение.
Эйфорическая

12
@Telastyn Это не то, что говорит принцип открытого / закрытого. В противном случае метод, который принимает параметры, будет нарушать OCP, потому что я мог бы «изменить» его поведение, передав ему другие параметры. Точно так же в соответствии с вашей версией OCP, он будет находиться в прямом конфликте с DI, что делает довольно странным, что оба появляются в аббревиатуре SOLID.
Бен Ааронсон

14

Обязательно ли использование контейнера IoC нарушает принципы OOP / SOLID? Нет .

Можете ли вы использовать контейнеры IoC таким образом, чтобы они нарушали принципы OOP / SOLID? Да .

Контейнеры IoC - это просто рамки для управления зависимостями в вашем приложении.

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

Например, в ASP.NET WebForms вам необходимо использовать внедрение свойств, поскольку создать экземпляр a невозможно Page. Это понятно, потому что WebForms никогда не разрабатывались с учетом строгих ООП-парадигм.

В ASP.NET MVC, с другой стороны, вполне возможно использовать контейнер IoC, используя очень дружественный для OOP / SOLID инжектор конструктора.

Я полагаю, что в этом сценарии (с использованием инжектора конструктора) он продвигает принципы SOLID по следующим причинам:

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

  • Открыто / Закрыто - это то, где использование контейнера IoC действительно блестяще, вы можете расширить функциональность класса путем внедрения различных зависимостей. Наличие зависимостей, которые вы вводите, позволяет вам изменить поведение этого класса. Хорошим примером является изменение класса, который вы вводите, написав декоратор, чтобы добавить кеширование или запись в журнал. Вы можете полностью изменить реализации ваших зависимостей, если написаны на соответствующем уровне абстракции.

  • Принцип замещения Лискова - не очень актуально

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

  • Инверсия зависимостей - контейнеры IoC позволяют вам легко инвертировать зависимости.

Вы определяете кучу зависимостей и объявляете отношения и области действия, и запускаете bing bang boom: у вас волшебным образом есть какое-то дополнение, которое в какой-то момент изменяет ваш код или IL и заставляет вещи работать. Это не является явным или прозрачным.

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

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

Это и есть причина, по которой OOP / SOLID делает системы управляемыми. Использование контейнера IoC соответствует этой цели.

Автор этого класса говорит: «Если бы мы не использовали контейнер IOC, конструктор имел бы дюжину параметров !!! Это ужасно!»

Класс с 12 зависимостями, вероятно, является нарушением SRP, независимо от того, в каком контексте вы его используете.


2
Отличный ответ. Кроме того, я думаю, что все основные контейнеры IOC также поддерживают AOP, что может очень помочь с OCP. Для сквозных задач AOP обычно является более дружественным к OCP маршрутом, чем декоратор.
Бен Ааронсон

2
@BenAaronson Учитывая, что AOP внедряет код в класс, я бы не назвал его более удобным для OCP. Вы принудительно открываете и меняете класс во время компиляции / выполнения.
Доваль

1
@Doval Я не понимаю, как он внедряет код в класс. Заметьте, я говорю о стиле Castle DynamicProxy, где у вас есть перехватчик, а не стиль IL-ткачества. Но перехватчик - это, по сути, просто декоратор, который использует некоторое отражение, чтобы ему не приходилось связываться с конкретным интерфейсом.
Бен Ааронсон

«Инверсия зависимостей - контейнеры IoC позволяют легко выполнять инверсию зависимостей», - они этого не делают, они просто используют что-то полезное, независимо от того, есть ли у вас IoC или нет. То же самое с другими принципами.
День

Я действительно не понимаю логики объяснения того, что что-то не плохо, на основании того факта, что это основа. ServiceLocators также сделаны как фреймворки и считаются плохими :).
17

5

Разве контейнеры IOC не ломают и не позволяют кодировщикам ломать много принципов OOP / SOLID?

staticКлючевое слово в C # позволяет разработчикам разбить много ООП / ТВЕРДЫХ принципы , но он по - прежнему является действительным инструментом в правильных обстоятельствах.

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

Так что, хотя эта напыщенная речь поднимает некоторые правильные вопросы о плохо написанном коде, это не совсем вопрос, и он не совсем полезен.


Я понимаю контейнеры IOC, что для управления областью действия, лени и доступности он изменяет скомпилированный код для выполнения действий, которые нарушают OOP / SOLID, но скрывает эту реализацию под хорошей структурой. Но да, это дополнительно открывает другие двери для неправильного использования. Лучшим ответом на мой вопрос было бы разъяснение о том, как реализованы контейнеры IOC, которое дополняет или опровергает результаты моих собственных исследований.
Суамере

2
@ Суамере - я думаю, вы используете слишком широкое определение МОК. Тот, который я использую, не изменяет скомпилированный код. Существует множество других инструментов, не относящихся к МОК, которые также модифицируют скомпилированный код. Возможно, ваш заголовок должен быть больше похож на: «Изменяет ли измененный скомпилированный код ...»
Cerad

@Suamere Я не настолько опытен с IoC, но я никогда не слышал о том, чтобы IoC менял скомпилированный код. Не могли бы вы предоставить информацию о том, о каких платформах / языках / фреймворках вы говорите?
Эйфорическое

1
«Контейнер IoC может использоваться для упрощения следования принципам SOLID». - не могли бы вы уточнить, пожалуйста? Объяснение того, как конкретно это помогает с каждым принципом, было бы полезно. И, конечно, использование чего-либо - это не то же самое, что помочь чему-то случиться (например, DI).
День

1
@Euphoric Я не знаю, о каких фреймворках .net говорил Суамер, но Spring, конечно же, делает скрытые изменения кода. Наиболее очевидным примером является его поддержка внедрения на основе методов (при этом он переопределяет абстрактный метод в вашем классе, чтобы вернуть зависимость, которой он управляет). Он также широко использует автоматически сгенерированные прокси-серверы, чтобы обернуть ваш код для обеспечения дополнительного поведения (например, для автоматического управления транзакциями). Однако многое из этого на самом деле не связано с его ролью структуры DI.
Жюль

3

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

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

Я не понимаю, откуда в этом проблема с принципом открытого-закрытого, поэтому я не буду это комментировать. Но вам не хватает одной части SOLID, которая имеет на это большое влияние: принцип инверсии зависимости. Если вы будете следовать DIP, вы обнаружите, что инвертирование зависимости вводит абстракцию, реализацию которой необходимо решить при создании экземпляра класса. При таком подходе вы получите множество классов, которые требуют реализации нескольких интерфейсов и обеспечения их правильной работы. Если бы вы затем предоставили эти зависимости вручную, вам пришлось бы сделать много дополнительной работы. Контейнеры IoC облегчают выполнение этой работы и даже позволяют изменять ее без перекомпиляции программы.

Итак, ответ на ваш вопрос - нет. Контейнеры IoC не нарушают принципы проектирования ООП. Напротив, они решают проблемы, вызванные соблюдением принципов SOLID (особенно принципа Dependency-Inversion).


Недавно попробовал применить IoC (Autofac) к нетривиальному проекту. Понял, что мне нужно было сделать аналогичную работу с неязыковыми конструкциями (new () vs API): указав, что следует разрешать в интерфейсах, а что разрешать в конкретных классах, указав, что должно быть экземпляром, а что должно быть одиночным, убедившись, что введение свойства работает правильно, чтобы уменьшить круговую зависимость. Решил отказаться. Хорошо работает с контроллерами в ASP MVC, хотя.
День

@Den Ваш проект не был явно разработан с учетом инверсии зависимостей. И нигде я не говорил, что использование IoC тривиально.
Эйфорическое

На самом деле, эта вещь - я использую Deverdency Inversion с самого начала. IoC, влияющий на дизайн, - это мое самое большое беспокойство. Например, зачем мне везде использовать интерфейсы, чтобы сделать IoC проще? Также смотрите верхний комментарий по этому вопросу.
День

@ Технически не нужно использовать интерфейсы. Интерфейсы помогают с макетом для юнит-тестирования. Вы также можете пометить вещи, которые вам нужно виртуальные издеваться.
Фред
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.