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

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

Однако из-за необходимости удовлетворения зависимостей контейнер DI также должен знать об интерфейсах и классах, к которым в противном случае он не нуждался бы в доступе. Например:

Слой пользовательского интерфейса должен работать с IWidgetManager интерфейсом. Настроенная реализация - ConcreteWidgetManager. Это зарегистрировано в контейнере DI.

ConcreteWidgetManager зависит от IWidgetRepository. Желаемая реализация для этого - ConcreteWidgetRepository. Таким образом, контейнер DI также должен знать о IWidgetRepository и ConcreteWidgetRepository.

Если бы я просто жестко закодировал зависимость ConcreteWidgetManager от ConcreteWidgetRepository, то ConcreteWidgetRepository можно было бы сделать внутренним и, следовательно, невидимым для моего уровня пользовательского интерфейса. Никакой код пользовательского интерфейса не может обойти уровень менеджера и работать напрямую со слоем репозитория.

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

Прав ли я, что так думаю, и есть ли способ смягчить ситуацию?


person David    schedule 14.09.2011    source источник
comment
Ответ - да, DI нарушает инкапсуляцию. Это дубликат stackoverflow.com/questions/1005473/, см. также bryancook.net/2011/08/   -  person Amittai Shapira    schedule 14.09.2011
comment
Как ConcreteWidgetRepository виден слою пользовательского интерфейса? Тот факт, что он упоминается в конфигурации фреймворка, не означает, что он виден, если только ваш код пользовательского интерфейса не имеет автоматически подключенной ссылки на IWidgetRepository. И это проблема кодирования, точно эквивалентная вашему коду пользовательского интерфейса, содержащему жестко запрограммированную ссылку на ConcreteWidgetRepository   -  person parsifal    schedule 14.09.2011
comment
@parsifal - контейнер DI является частью уровня пользовательского интерфейса. Если ему нужно знать о ConcreteWidgetRepository (что он делает, чтобы вставить его в ConcreteWidgetManager), то эти знания также свободно доступны в другом месте на уровне пользовательского интерфейса.   -  person David    schedule 14.09.2011
comment
@David - на мой взгляд, контейнер DI является приложением: он связывает пользовательский интерфейс с бизнес-логикой и связывает любые внутренние зависимости, но не является частью ни того, ни другого .   -  person parsifal    schedule 14.09.2011
comment
@parsifal - я не знаю, на какой платформе вы разрабатываете, поэтому я не совсем уверен, как сформулировать вопрос, поэтому я дам вам его стиль .NET. Как же тогда вы создадите свое решение? Контейнер DI находится в проекте, отличном от проекта пользовательского интерфейса (например, в веб-приложении)?   -  person David    schedule 14.09.2011
comment
@ Дэвид - справедливый вопрос. Я думал в терминах Spring MVC, в котором есть четкое разделение между контроллером и представлением. Объекты, реализующие эти компоненты, могут происходить из одного и того же проекта или из разных (зависимых) проектов. Но без проводки, обеспечиваемой файлом beans.xml, эти независимые компоненты не знали бы друг о друге. Таким образом, мое заявление о том, что файл конфигурации (т.е. фреймворк) является приложением.   -  person parsifal    schedule 14.09.2011
comment
@parsifal - я узнал, что решение этой проблемы состоит в том, чтобы поместить логику разрешения DI в другую сборку, отличную от уровня пользовательского интерфейса. Затем сборка DI может знать обо всех реализациях, а сборка пользовательского интерфейса не обязана.   -  person David    schedule 13.10.2011


Ответы (3)


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

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

person Steven    schedule 15.09.2011
comment
Здесь есть над чем подумать. Спасибо. - person David; 19.09.2011
comment
Я хотел бы потратить некоторое время на создание быстрого решения и посмотреть, смогу ли я заставить этот подход работать. Если это сработает, я дам вам очки! - person David; 19.09.2011
comment
Хорошо, я поэкспериментировал с вашим предложением о размещении интерфейсов и реализаций в разных сборках, и оно работает! Сборке пользовательского интерфейса нужна ссылка на сборку интерфейса и на сборку «проводки», которая выполняет разрешение DI. Сборка проводки имеет ссылку как на сборку интерфейса, так и на сборку реализации. Таким образом, сборка пользовательского интерфейса не имеет прямой ссылки на сборку реализации, но по-прежнему получает внедренные реализации! Это абсолютно ответ на мой вопрос. - person David; 13.10.2011

То, как вы это описываете, подразумевает, что уровень пользовательского интерфейса отвечает за создание контейнера DI.

Это может быть верно в вашем конкретном приложении. Но помните, что инициализация - это крошечная часть всего кода.

Да, есть НЕКОТОРЫЙ код, который создает контейнер DI и уровень пользовательского интерфейса в определенном порядке. Возможно, уровень пользовательского интерфейса вызывает функцию CreateDIContainer, которая инициализирует все компоненты. Но эта функция - единственный случай, когда упоминаются реализации; все остальные аспекты уровня пользовательского интерфейса имеют дело с абстрактными интерфейсами. Пурист может усомниться, но на самом деле в 99,5% кода, который НЕ ЯВЛЯЕТСЯ CreateDIContainer, пользовательский интерфейс не знает, каковы реализации. Переместите функцию инициализации в отдельный статический класс в отдельной библиотеке, если это вас радует.


Меня также интересуют ваши слова

Если бы я просто жестко запрограммировал зависимость ConcreteWidgetManager от ConcreteWidgetRepository ....,

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

Вы хотите поиздеваться над ConcreteWidgetRepository? Если нет, то продолжайте и жестко закодируйте отношения. В противном случае вы просто вводите архитектуру ради архитектуры.

(См. Вам это не понадобится.)

person Andrew Shepherd    schedule 14.09.2011
comment
контейнер DI составляет лишь небольшую часть кода уровня пользовательского интерфейса, но тот факт, что ему необходимо знать об этих классах, означает, что остальная часть уровня пользовательского интерфейса тоже. - person David; 14.09.2011
comment
Ваш вопрос о том, нужно ли жестко кодировать зависимость, является интересным. Вопрос в том, насколько вероятно, что я захочу еще одну реализацию? Иногда возникают «рабочие» причины, по которым мне нужна другая реализация (например, смена провайдера), но чаще это делается для целей модульного тестирования. Мой инстинкт состоит в том, чтобы использовать интерфейс и DI для любого класса обслуживания, если я не уверен, что мне не понадобится другая реализация. - person David; 14.09.2011
comment
Под «служебным» классом я подразумеваю все, что демонстрирует поведение, а не просто состояние. - person David; 14.09.2011
comment
@David - О том, нужно ли жестко кодировать зависимость - вот где я следую принципу YAGNI. (c2.com/cgi/wiki?YouArentGonnaNeedIt) Нет причин, по которым вы не можете сделать это внедренная зависимость позже, когда вы знаете, что это решит проблему. - person Andrew Shepherd; 15.09.2011
comment
Круто, спасибо за мысли. Этот вопрос действительно заставил меня снова задуматься о DI. - person David; 19.09.2011

Да, инкапсуляция нарушается. Но это цена, которую вам нужно заплатить за использование DI. Это делает ваш код более тестируемым, но да, это нарушает инкапсуляцию.

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

person Ravi Bhatt    schedule 15.09.2011