Какова цель контейнеров МОК? Объединенные причины этого могут быть упрощены до следующего:
При использовании принципов разработки OOP / SOLID Dependency Injection становится беспорядочным. Либо у вас есть точки входа верхнего уровня, управляющие зависимостями для нескольких уровней ниже их самих и рекурсивно пропускающие зависимости через конструкцию, либо у вас есть несколько дублирующих код в шаблонах фабрики / сборщика и интерфейсах, которые строят зависимости по мере необходимости.
Не существует ООП / ТВЕРДЫХ способов сделать это И иметь очень красивый код.
Если это предыдущее утверждение верно, то как Контейнеры IOC делают это? Насколько я знаю, они не используют какой-то неизвестный метод, который нельзя сделать с помощью ручного DI. Поэтому единственное объяснение состоит в том, что контейнеры IOC нарушают принципы OOP / SOLID, используя частные средства доступа к статическим объектам.
Контейнеры IOC нарушают следующие принципы за кулисами? Это реальный вопрос, так как у меня хорошее понимание, но есть ощущение, что кто-то другой понимает лучше:
- Контроль области . Scope - это причина почти каждого решения, которое я принимаю в отношении дизайна кода. Блок, Локальный, Модуль, Статический / Глобальный. Область действия должна быть очень четкой, как можно больше на уровне блоков и как можно меньше в Static. Вы должны увидеть объявления, экземпляры и окончания жизненного цикла. Я доверяю языку и GC, чтобы управлять областью, пока я ясно с ним. В своем исследовании я обнаружил, что контейнеры IOC устанавливают большинство или все зависимости как статические и контролируют их через некоторую реализацию AOP за кулисами. Так что ничего не прозрачно.
- Инкапсуляция . Какова цель инкапсуляции? Почему мы должны держать частных членов так? По практическим соображениям разработчики нашего API не могут нарушить реализацию, изменив состояние (которым должен управлять класс владельца). Но также, по соображениям безопасности, инъекции не могут происходить так, чтобы обгонять наши государства-члены и обходить валидацию и контроль классов. Так что все (платформы Mocking или IOC), которые каким-то образом внедряют код перед компиляцией, чтобы предоставить внешний доступ частным членам, довольно огромно.
- Принцип единой ответственности . На первый взгляд, Контейнеры МОК, кажется, делают вещи чище. Но представьте, как бы вы сделали то же самое без вспомогательных структур. У вас будут конструкторы с дюжиной зависимостей, которые будут переданы. Это не значит, что нужно закрывать их контейнерами IOC, это хорошо! Это знак перефакторинга вашего кода и следования SRP.
Открыто / Закрыто . Точно так же, как SRP не только для класса (я применяю SRP к линиям с одной ответственностью, не говоря уже о методах). Open / Closed - это не просто теория высокого уровня, чтобы не изменять код класса. Это практика понимания конфигурации вашей программы и контроля над тем, что изменяется и что расширяется. Контейнеры IOC могут частично изменить работу ваших классов, потому что:
а. Основной код не определяет распределение зависимостей, а конфигурация фреймворка.
б. Область действия может быть изменена во время, которое не контролируется вызывающими участниками, а вместо этого определяется внешне статической структурой.
Таким образом, конфигурация класса на самом деле не закрыта, она меняется, основываясь на конфигурации стороннего инструмента.
Причина в том, что это вопрос, потому что я не обязательно являюсь мастером всех контейнеров МОК. И хотя идея контейнера IOC хороша, они кажутся просто фасадом, который скрывает плохую реализацию. Но чтобы выполнить внешний контроль Scope, доступ к приватному члену и отложенную загрузку, необходимо выполнить много нетривиальных сомнительных вещей. AOP великолепен, но способ, которым это достигается через контейнеры IOC, также сомнителен.
Я могу доверять C # и .NET GC, чтобы делать то, что я ожидаю. Но я не могу доверить то же самое стороннему инструменту, который изменяет мой скомпилированный код для выполнения обходных решений, которые я не смог бы сделать вручную.
Например: Entity Framework и другие ORM создают строго типизированные объекты и сопоставляют их с объектами базы данных, а также предоставляют базовые функциональные возможности для выполнения CRUD. Любой желающий может создать свой собственный ORM и продолжать следовать принципам OOP / SOLID вручную. Но эти рамки помогают нам, поэтому нам не нужно каждый раз заново изобретать колесо. В то время как контейнеры IOC, похоже, помогают нам целенаправленно работать над принципами ООП / ТВЕРДЫЕ и скрывать это.
IOC
иExplicitness of code
это именно то, с чем у меня проблемы. Ручной DI может легко стать рутиной, но он по крайней мере помещает самодостаточный явный поток программ в одно место - «Вы получаете то, что видите, вы видите то, что получаете». В то время как привязки и объявления контейнеров МОК могут легко стать скрытой параллельной программой.Ответы:
Я расскажу о ваших пунктах численно, но, во-первых, есть кое-что, к чему вы должны быть очень осторожны: не путайте, как потребитель использует библиотеку с тем, как она реализована . Хорошими примерами этого являются Entity Framework (который вы сами цитируете как хорошую библиотеку) и MVC ASP.NET. Оба из них делают очень многое под капотом, например, с отражением, которое абсолютно не будет считаться хорошим дизайном, если вы распространяете его с помощью повседневного кода.
Эти библиотеки абсолютно не «прозрачны» в том, как они работают или что они делают за кулисами. Но это не вредно, потому что они поддерживают хорошие принципы программирования у своих потребителей . Поэтому, когда вы говорите о такой библиотеке, помните, что, как пользователь библиотеки, вы не должны беспокоиться о ее реализации или обслуживании. Вам следует беспокоиться только о том, как это помогает или мешает написанному вами коду, который использует библиотеку. Не путайте эти понятия!
Итак, чтобы пройти через точку:
Сразу же мы достигаем того, что я могу только предположить, является примером выше. Вы говорите, что контейнеры IOC устанавливают большинство зависимостей как статические. Что ж, возможно, некоторые подробности реализации того, как они работают, включают статическое хранилище (хотя, учитывая, что они, как правило, имеют в
IKernel
качестве основного хранилища экземпляр объекта типа Ninject , я даже сомневаюсь в этом). Но это не ваша забота!На самом деле, контейнер IoC так же явен с областью действия, как и инъекция зависимостей бедного человека. (Я собираюсь продолжать сравнивать контейнеры IoC с DI бедного человека, потому что сравнивать их с DI вообще не было бы несправедливым и вводящим в заблуждение. И, чтобы быть ясным, я не использую «DI бедного человека» как уничижительный.)
В DI бедного человека вы создаете зависимость вручную, а затем внедряете ее в класс, который в ней нуждается. В момент, когда вы строите зависимость, вы выбираете, что с ней делать - сохраняете ее в локальной переменной, переменной класса, статической переменной, вообще не сохраняйте ее. Вы можете передать один и тот же экземпляр множеству классов или создать новый для каждого класса. Без разницы. Суть в том, что для того, чтобы увидеть, что происходит, вы смотрите на точку - возможно, рядом с корнем приложения - где создается зависимость.
А как насчет контейнера IoC? Ну, вы делаете точно так же! Опять же , идя по терминологии Ninject, вы посмотрите на то, где связывание установлено, и найти , говорит ли это что - то вроде
InTransientScope
,InSingletonScope
или что угодно. Во всяком случае, это может быть более понятным, потому что у вас есть код, объявляющий его область действия, вместо того, чтобы искать метод для отслеживания того, что происходит с каким-либо объектом (объект может быть ограничен до блока, но используется ли он несколько раз в этом например, блок или только один раз). Поэтому, возможно, вас отталкивает мысль о необходимости использовать функцию в контейнере IoC, а не функцию необработанного языка, чтобы определять область действия, но если вы доверяете своей библиотеке IoC, что и должно быть! - в этом нет реального недостатка ,Я до сих пор не знаю, о чем ты здесь говоришь. Контейнеры IoC рассматривают частные свойства как часть их внутренней реализации? Я не знаю, почему они это сделали, но, опять же, если они это сделают, то вам не важно, как реализована используемая вами библиотека.
Или, может быть, они предоставляют такую возможность, как инъекция в частные сеттеры? Честно говоря, я никогда не сталкивался с этим, и я сомневаюсь, является ли это общей чертой. Но даже если он есть, это простой случай использования инструмента, который может быть использован не по назначению. Помните, что даже без контейнера IoC, это всего лишь несколько строк кода Reflection для доступа и изменения частного свойства. Это то, что вы почти никогда не должны делать, но это не значит, что .NET плохо раскрывает возможности. Если кто-то так явно и дико злоупотребляет инструментом, это вина человека, а не инструмента.
Конечная точка здесь аналогична 2. В подавляющем большинстве случаев контейнеры IoC действительно используют конструктор! Внедрение сеттера предлагается для очень специфических обстоятельств, когда по определенным причинам инъекция конструктора не может быть использована. Любой, кто все время использует инъекцию сеттера, чтобы скрыть, сколько зависимостей передается, пользуется этим инструментом. Это не вина инструмента, это их.
Теперь, если это была действительно простая ошибка, которую невиновно совершить, и та, которую поощряют контейнеры IoC, тогда ладно, возможно, у вас есть смысл. Это все равно, что сделать каждого члена моего класса публичным, а потом обвинять других людей, когда они изменяют то, что не должны, верно? Но любой, кто использует сеттерную инъекцию, чтобы скрыть нарушения SRP, либо преднамеренно игнорирует, либо полностью игнорирует основные принципы проектирования. Необоснованно возлагать вину за это на контейнер IoC.
Это особенно верно, потому что это также то, что вы можете сделать так же легко с DI бедного человека:
На самом деле, это беспокойство кажется совершенно ортогональным по отношению к тому, используется ли контейнер IoC.
Изменение того, как ваши классы работают вместе, не является нарушением OCP. Если это так, то любая инверсия зависимостей будет поощрять нарушение OCP. Если бы это было так, они не были бы в одном и том же сплошном сокращении!
Более того, ни пункты а), ни б) не приблизились к тому, чтобы иметь какое-либо отношение к OCP. Я даже не знаю, как ответить на них в отношении OCP.
Единственное, что я могу догадаться, это то, что вы думаете, что OCP связан с поведением, которое не изменяется во время выполнения, или с тем, откуда в коде контролируется жизненный цикл зависимостей. Это не. В OCP нет необходимости изменять существующий код при добавлении или изменении требований. Все дело в написании кода, а не в том, как код, который вы уже написали, склеен (хотя, конечно, слабая связь является важной частью достижения OCP).
И последнее, что вы скажете:
Да, вы можете . У вас нет абсолютно никаких оснований полагать, что эти инструменты, на которые опирается огромное количество проектов, более подвержены ошибкам или склонны к неожиданному поведению, чем любая другая сторонняя библиотека.
добавление
Я только что заметил, что ваши вступительные абзацы тоже могут использовать некоторую адресацию. Вы сардонически говорите, что контейнеры IoC «не используют какой-то секретный метод, о котором мы никогда не слышали», чтобы избежать беспорядочного, склонного к дублированию кода для построения графа зависимостей. И вы совершенно правы: на самом деле они обращаются к этим вещам с помощью тех же самых базовых методов, которые мы, программисты, всегда делаем.
Позвольте мне рассказать вам о гипотетическом сценарии. Вы, как программист, собрали большое приложение, и в точке входа, где вы строите свой граф объектов, вы замечаете, что у вас довольно грязный код. Существует довольно много классов, которые используются снова и снова, и каждый раз, когда вы создаете один из них, вам приходится снова создавать целую цепочку зависимостей. Кроме того, вы обнаружите, что у вас нет какого-либо выразительного способа объявления или управления жизненным циклом зависимостей, кроме как с помощью пользовательского кода для каждой из них. Ваш код неструктурирован и полон повторений. Это беспорядок, о котором вы говорите во вступительном абзаце.
Итак, во-первых, вы начинаете рефакторингить немного - когда некоторый повторяющийся код достаточно структурирован, вы извлекаете его в вспомогательные методы и так далее. Но затем вы начинаете думать - это проблема, которую я мог бы решить в общем смысле, которая не является специфической для этого конкретного проекта, но может помочь вам во всех ваших будущих проектах?
Итак, вы садитесь, думаете об этом и решаете, что должен быть класс, который может разрешать зависимости. И вы набросаете, какие публичные методы понадобятся:
Bind
говорит "где вы видите аргумент конструктора типаinterfaceType
, передавайте экземплярconcreteType
". Дополнительныйsingleton
параметр указывает, использовать ли один и тот же экземплярconcreteType
каждый раз или всегда создавать новый.Resolve
просто попытается создать егоT
с помощью любого конструктора, в котором можно найти аргументы всех типов, которые были ранее связаны. Он также может вызывать себя рекурсивно, чтобы разрешить зависимости полностью вниз. Если он не может разрешить экземпляр, потому что не все было связано, он генерирует исключение.Вы можете попробовать реализовать это самостоятельно, и вы обнаружите, что вам нужно немного поразмышлять и немного кешировать привязки, где
singleton
это правда, но, конечно, ничего радикального или ужасающего. И как только вы закончите - вуаля , у вас будет ядро вашего собственного контейнера IoC! Это действительно так страшно? Единственная реальная разница между этим и Ninject, StructureMap, Castle Windsor или любым другим, который вы предпочитаете, заключается в том, что они обладают гораздо большей функциональностью, чтобы покрыть (многие!) Случаи использования, когда этой базовой версии было бы недостаточно. Но в основе того, что у вас есть, лежит сущность контейнера IoC.источник
В теории? Нет. Контейнеры IoC - это всего лишь инструмент. У вас могут быть объекты с единственной ответственностью, хорошо разделенные интерфейсы, изменение их поведения после записи и т. Д.
На практике? Абсолютно.
Я имею в виду, что я слышал, как немногие программисты говорят, что они используют контейнеры IoC специально, чтобы позволить вам использовать некоторую конфигурацию для изменения поведения системы - нарушая принцип Open / Closed. Раньше были шутки о кодировании в XML, потому что 90% их работы заключались в исправлении конфигурации Spring. И вы, безусловно, правы, что сокрытие зависимостей (что, правда, делают не все контейнеры IoC) - это быстрый и простой способ создания высокосвязанного и очень хрупкого кода.
Давайте посмотрим на это по-другому. Давайте рассмотрим очень простой класс, который имеет одну базовую зависимость. Это зависит от
Action
делегата или функтора, который не принимает параметров и возвращаетvoid
. Какова ответственность этого класса? Ты не можешь сказать. Я мог бы назвать эту зависимость чем-то таким,CleanUpAction
что заставляет вас думать, что она делает то, что вы хотите, чтобы она делала. Как только IoC вступает в игру, он становится чем угодно . Любой может зайти в конфигурацию и изменить свое программное обеспечение, чтобы сделать довольно много произвольных вещей."Как это отличается от передачи в
Action
конструктор?" ты спрашиваешь. Это отличается тем, что вы не можете изменить то, что передается в конструктор после развертывания программного обеспечения (или во время выполнения!). Это отличается тем, что ваши юнит-тесты (или юнит-тесты ваших потребителей) охватили сценарии, когда делегат передается в конструктор.«Но это фиктивный пример! Мои вещи не позволяют менять поведение!» Вы восклицаете. Извините, что сказал вам, но это так. Если интерфейс, который вы вводите, это просто данные. И ваш интерфейс - это не просто данные, потому что, если бы это были просто данные, вы бы строили простой объект и использовали его. Как только у вас есть виртуальный метод в точке ввода, контракт вашего класса с использованием IoC будет «Я могу сделать все, что угодно ».
«Чем это отличается от использования сценариев для управления вещами? Это прекрасно делать». некоторые из вас спрашивают. Это не отличается С точки зрения разработки программы, нет никакой разницы. Вы вызываете свою программу для запуска произвольного кода. И конечно, это было сделано в играх целую вечность. Это сделано в тоннах инструментов системного администрирования. Это ООП? На самом деле, нет.
Там нет оправдания за их нынешнюю повсеместность. Контейнеры IoC имеют одну твердую цель - склеить ваши зависимости, когда у вас так много их комбинаций, или они перемещаются так быстро, что нецелесообразно делать эти зависимости явными. Поэтому очень немногие предприятия действительно соответствуют этому сценарию.
источник
Обязательно ли использование контейнера 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 позволяют вам легко инвертировать зависимости.
Он явный и прозрачный, вам нужно указать контейнеру IoC, как связывать зависимости и как управлять временем жизни объектов. Это может быть либо по соглашению, либо по файлам конфигурации, либо по коду, написанному в основных приложениях вашей системы.
Это и есть причина, по которой OOP / SOLID делает системы управляемыми. Использование контейнера IoC соответствует этой цели.
Класс с 12 зависимостями, вероятно, является нарушением SRP, независимо от того, в каком контексте вы его используете.
источник
static
Ключевое слово в C # позволяет разработчикам разбить много ООП / ТВЕРДЫХ принципы , но он по - прежнему является действительным инструментом в правильных обстоятельствах.Контейнеры IoC обеспечивают хорошо структурированный код и снижают когнитивную нагрузку на разработчиков. Контейнер IoC может быть использован для упрощения следования принципам SOLID. Конечно, этим можно злоупотреблять, но если мы исключим использование какого-либо инструмента, который может быть использован не по назначению, нам придется полностью отказаться от программирования.
Так что, хотя эта напыщенная речь поднимает некоторые правильные вопросы о плохо написанном коде, это не совсем вопрос, и он не совсем полезен.
источник
Я вижу множество ошибок и недоразумений в вашем вопросе. Первая из них заключается в том, что вы пропускаете различие между внедрением свойства / поля, внедрением конструктора и указателем службы. Было много дискуссий (например, пламенных войн) о том, как правильно делать вещи. В вашем вопросе вы, кажется, предполагаете только внедрение свойства и игнорируете внедрение конструктора.
Во-вторых, вы предполагаете, что контейнеры IoC как-то влияют на дизайн вашего кода. Они не делают, или, по крайней мере, не должно быть необходимости изменять дизайн вашего кода, если вы используете IoC. Ваши проблемы с классами со многими конструкторами - это случай, когда IoC скрывает реальные проблемы с вашим кодом (наиболее вероятно, с классом, содержащим прерывания SRP), и скрытые зависимости являются одной из причин, по которым люди не рекомендуют использовать внедрение свойств и указатель службы.
Я не понимаю, откуда в этом проблема с принципом открытого-закрытого, поэтому я не буду это комментировать. Но вам не хватает одной части SOLID, которая имеет на это большое влияние: принцип инверсии зависимости. Если вы будете следовать DIP, вы обнаружите, что инвертирование зависимости вводит абстракцию, реализацию которой необходимо решить при создании экземпляра класса. При таком подходе вы получите множество классов, которые требуют реализации нескольких интерфейсов и обеспечения их правильной работы. Если бы вы затем предоставили эти зависимости вручную, вам пришлось бы сделать много дополнительной работы. Контейнеры IoC облегчают выполнение этой работы и даже позволяют изменять ее без перекомпиляции программы.
Итак, ответ на ваш вопрос - нет. Контейнеры IoC не нарушают принципы проектирования ООП. Напротив, они решают проблемы, вызванные соблюдением принципов SOLID (особенно принципа Dependency-Inversion).
источник