#c# #asp.net-mvc #dependency-injection #inversion-of-control #structuremap
#c# #asp.net-mvc #внедрение зависимостей #инверсия управления #structuremap
Вопрос:
В настоящее время я пытаюсь лучше понять внедрение зависимостей и использую asp.net MVC для работы с ним. Возможно, вы увидите от меня другие связанные с этим вопросы 😉
Хорошо, я начну с примера контроллера (примера менеджера контактов asp.net Приложение MVC)
public class ContactsController{
ContactsManagerDb _db;
public ContactsController(){
_db = ContactsManagerDb();
}
//...Actions here
}
Хорошо, потрясающе, что это работает. Все мои действия могут использовать базу данных для действий CRUD. Теперь я решил, что хочу добавить модульное тестирование, и добавил еще один конструктор для моделирования базы данных
public class ContactsController{
IContactsManagerDb _db;
public ContactsController(){
_db = ContactsManagerDb();
}
public ContactsController(IContactsManagerDb db){
_db = db;
}
//...Actions here
}
Потрясающе, это работает, в моих модульных тестах я могу создать свою собственную реализацию IContactsManagerDb
и модульно протестировать свой контроллер.
Теперь люди обычно принимают следующее решение (и вот мой актуальный вопрос), избавляются от пустого контроллера и используют внедрение зависимостей, чтобы определить, какую реализацию использовать.
Итак, используя StructureMap, я добавил следующее правило внедрения:
x.For<IContactsManagerDb>().Use<ContactsManagerDb>();
И, конечно, в моем тестовом проекте я использую другую IContactsManagerDb
реализацию.
x.For<IContactsManagerDb>().Use<MyTestingContactsManagerDb>();
Но мой вопрос в том, ** Какую проблему я решил или что я упростил, используя внедрение зависимостей в этом конкретном случае «
Я не вижу никакого практического применения этого сейчас, я понимаю, КАК, но не ПОЧЕМУ? Какая от этого польза? Может ли кто-нибудь добавить к этому проекту, возможно, привести пример того, как это более практично и полезно?
Комментарии:
1. Не могли бы вы протестировать этот код перед добавлением введенного объекта?
2. Да, верно? Потому что я перегрузил конструктор и просто вставил его вручную с помощью моего
MyTestinContactsManagerDb
3. Итак, вы сделали конкретную реализацию зависимой от тестового класса? Позвольте мне перефразировать это: вы соединили свой тестируемый класс с классом, используемым для тестирования? Это то, что вы получаете при внедрении, вы разделяете классы, делаете их независимыми.
4. Ах да, конечно, вы правы. Это действительно глупо: P
Ответ №1:
Первый пример не поддается модульному тестированию, поэтому он не очень хорош, поскольку создает сильную связь между различными уровнями вашего приложения и делает их менее пригодными для повторного использования. Второй пример называется внедрением зависимостей для бедных людей. Это также обсуждается здесь.
Что не так с внедрением зависимостей для бедных людей, так это то, что код не является автодокументируемым. Он не сообщает о своих намерениях потребителю. Пользователь видит этот код, и он может легко вызвать конструктор по умолчанию, не передавая никаких аргументов, тогда как, если бы не было конструктора по умолчанию, сразу было бы ясно, что этот класс абсолютно требует, чтобы какой-то контракт был передан его конструктору для нормальной работы. И на самом деле не классу решать, какую конкретную реализацию выбрать. Это зависит от потребителя этого класса.
Комментарии:
1. Мне не нравится этот способ сказать, что DI / IOC предназначен для модульного тестирования. На самом деле, если это только для модульного тестирования, вы не должны этого делать.
2. @manojlds, я абсолютно согласен с вами. Самым большим преимуществом DI / IOC является достижение недельной связи между уровнями кода, что делает их более удобными для повторного использования в разных контекстах / приложениях. Модульное тестирование — это просто дополнительное преимущество, которое вы получаете от соединения this week, поскольку оно позволяет вам тестировать эти слои изолированно.
3. Серьезно, Дарин, я уже довольно давно борюсь с внедрением зависимостей. Ваше объяснение и веб-сайт, на который вы ссылались, описывают это очень четко, и впервые у меня наконец-то появилось ощущение, что я начинаю понимать это! Вы действительно заслуживаете моей благодарности!!!!
4. Вы также должны добавить контроль над временем жизни объекта. Использование poor man DI далеко не так удобно, как использование контейнера IoC.
Ответ №2:
Внедрение зависимостей полезно по 3 основным причинам :
- Это метод разделения интерфейсов и реализаций.
- Это хорошо для уменьшения количества методов котельной плиты / фабрики в приложении.
- Это увеличивает модульность пакетов.
В качестве примера рассмотрим модульный тест, который требовал доступа к классу, определяемому как интерфейс. Во многих случаях модульный тест для интерфейса должен был бы вызывать реализации этого интерфейса — таким образом, если бы изменилась реализация, изменился бы и модульный тест. Однако с помощью DI вы можете «внедрить» реализацию интерфейса во время выполнения в модульный тест, используя API-интерфейс внедрения, так что изменения в реализациях должны обрабатываться только платформой внедрения, а не отдельными классами, которые используют эти реализации.
Другой пример из мира Интернета: рассмотрим связь между поставщиками услуг и определениями сервисов. Если конкретному компоненту требуется доступ к сервису — лучше разрабатывать для интерфейса, чем для конкретной реализации этого сервиса. Внедрение опять же позволяет реализовать такой дизайн, позволяя вам динамически добавлять зависимости, ссылаясь на вашу среду внедрения.
Таким образом, различные соединения классов друг с другом выводятся из фабрик и отдельных классов и обрабатываются единообразным, абстрактным, многоразовым и простым в обслуживании способом, если у вас есть хорошая структура DI. Лучшие учебные пособия по DI, которые я видел, находятся в руководствах Google по Guice, доступных на YouTube. Хотя это не то же самое, что ваша конкретная технология, принципы идентичны.
Комментарии:
1. Я думаю, что ваш ответ (а также вопрос OP) вводят в заблуждение шаблон DI и использование инфраструктуры DI.
Ответ №3:
Во-первых, ваш пример не будет компилироваться. var _db;
недопустимый оператор, потому что тип переменной должен быть выведен при объявлении.
Вы могли бы это сделать var _db = new ContactsManagerDb();
, но тогда ваш второй конструктор не будет компилироваться, потому что вы пытаетесь назначить IContactsManagerDb экземпляру ContactsManagerDb .
Вы могли бы изменить его на IContactsManagerDb _db;
, а затем убедиться, что ContactsManagerDb
это происходит от IContactsManagerDb
, но тогда это делает ваш первый конструктор бесполезным. У вас все равно должен быть конструктор, который принимает аргумент интерфейса, так почему бы просто не использовать его постоянно?
Внедрение зависимостей заключается в удалении зависимостей от самих классов. ContactsController
не нужно знать о ContactsManagerDb, чтобы использовать IContactsManagerDb для доступа к менеджеру контактов.
Комментарии:
1. Ах да, извини, ты прав насчет этого. Я изменил свой пост, я написал его неправильно, пытаясь упростить ситуацию. Спасибо, что указали на это/