Я расскажу о ваших пунктах численно, но, во-первых, есть кое-что, к чему вы должны быть очень осторожны: не путайте, как потребитель использует библиотеку с тем, как она реализована . Хорошими примерами этого являются 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 бедного человека:
var myObject
= new MyTerriblyLargeObject { DependencyA = new Thing(), DependencyB = new Widget(), Dependency C = new Repository(), ... };
На самом деле, это беспокойство кажется совершенно ортогональным по отношению к тому, используется ли контейнер IoC.
Изменение того, как ваши классы работают вместе, не является нарушением 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
иExplicitness of code
это именно то, с чем у меня проблемы. Ручной DI может легко стать рутиной, но он по крайней мере помещает самодостаточный явный поток программ в одно место - «Вы получаете то, что видите, вы видите то, что получаете». В то время как привязки и объявления контейнеров МОК могут легко стать скрытой параллельной программой.