Во-первых, я хочу объяснить предположение, которое я делаю для этого ответа. Это не всегда так, но довольно часто
Интерфейсы являются прилагательными; классы существительные.
(На самом деле, существуют интерфейсы, которые также являются существительными, но я хочу обобщить их здесь.)
Так, например, интерфейс может быть чем-то вроде IDisposable
, IEnumerable
или IPrintable
. Класс - это фактическая реализация одного или нескольких из этих интерфейсов: List
или они Map
могут быть обеими реализациями IEnumerable
.
Чтобы понять: часто ваши занятия зависят друг от друга. Например, у вас может быть Database
класс, который обращается к вашей базе данных (ха, сюрприз! ;-)), но вы также хотите, чтобы этот класс регистрировал доступ к базе данных. Предположим, у вас есть другой класс Logger
, затем Database
есть зависимость от Logger
.
Все идет нормально.
Вы можете смоделировать эту зависимость внутри вашего Database
класса с помощью следующей строки:
var logger = new Logger();
и все хорошо. Это хорошо до того дня, когда вы поймете, что вам нужно несколько регистраторов: иногда вы хотите войти в консоль, иногда в файловую систему, иногда используя TCP / IP и удаленный сервер журналирования, и так далее ...
И, конечно же, вы НЕ хотите менять весь свой код (пока у вас есть его миллиарды) и заменять все строки
var logger = new Logger();
по:
var logger = new TcpLogger();
Во-первых, это не весело. Во-вторых, это подвержено ошибкам. В-третьих, это глупая, повторяющаяся работа для обученной обезьяны. Ну так что ты делаешь?
Очевидно, что неплохо было бы представить интерфейс ICanLog
(или аналогичный), который реализован всеми различными регистраторами. Итак, шаг 1 в вашем коде - это то, что вы делаете:
ICanLog logger = new Logger();
Теперь вывод типа больше не меняет тип, у вас всегда есть один единственный интерфейс для разработки. Следующим шагом является то, что вы не хотите иметь new Logger()
снова и снова. Таким образом, вы наделяете надежность создания новых экземпляров одним центральным фабричным классом и получаете код, такой как:
ICanLog logger = LoggerFactory.Create();
Фабрика сама решает, какой именно регистратор создать. Ваш код больше не заботится, и если вы хотите изменить тип используемого регистратора, вы измените его один раз : внутри завода.
Теперь, конечно, вы можете обобщить эту фабрику и заставить ее работать для любого типа:
ICanLog logger = TypeFactory.Create<ICanLog>();
Где-то эта TypeFactory нуждается в данных конфигурации, какой фактический класс должен быть создан при запросе определенного типа интерфейса, поэтому вам необходимо сопоставление. Конечно, вы можете сделать это отображение внутри вашего кода, но тогда изменение типа означает перекомпиляцию. Но вы также можете поместить это отображение в файл XML, например. Это позволяет вам изменять фактически используемый класс даже после времени компиляции (!), Что означает динамически, без перекомпиляции!
Чтобы дать вам полезный пример для этого: подумайте о программном обеспечении, которое не регистрируется нормально, но когда ваш клиент звонит и просит помощи, потому что у него есть проблема, вы отправляете ему только обновленный файл конфигурации XML, и теперь у него есть ведение журнала включено, и ваша поддержка может использовать файлы журнала, чтобы помочь вашему клиенту.
И теперь, когда вы немного заменяете имена, вы получаете простую реализацию локатора служб , которая является одним из двух шаблонов инверсии управления (поскольку вы инвертируете контроль над тем, кто решает, какой именно класс создать).
В целом это уменьшает зависимости в вашем коде, но теперь весь ваш код имеет зависимость от центрального, единого локатора службы.
Внедрение зависимостей теперь является следующим шагом в этой строке: просто избавьтесь от этой единственной зависимости от локатора службы: вместо того, чтобы различные классы запрашивали у локатора службы реализацию для определенного интерфейса, вы - опять же - возвращаете контроль над тем, кто и как создает ,
С внедрением зависимостей ваш Database
класс теперь имеет конструктор, который требует параметр типа ICanLog
:
public Database(ICanLog logger) { ... }
Теперь ваша база данных всегда имеет регистратор для использования, но она больше не знает, откуда этот регистратор.
И вот здесь вступает в игру инфраструктура DI: вы снова настраиваете свои отображения, а затем просите свою инфраструктуру DI создать для вас экземпляр приложения. Поскольку Application
класс требует ICanPersistData
реализации, экземпляр Database
внедряется - но для этого он должен сначала создать экземпляр типа регистратора, для которого настроен ICanLog
. И так далее ...
Короче говоря: внедрение зависимостей - это один из двух способов удаления зависимостей в вашем коде. Это очень полезно для изменения конфигурации после компиляции и отлично подходит для модульного тестирования (поскольку позволяет очень легко вводить заглушки и / или макеты).
На практике есть вещи, которые вы не можете обойтись без локатора службы (например, если вы заранее не знаете, сколько экземпляров вам нужно для конкретного интерфейса: платформа DI всегда вводит только один экземпляр для каждого параметра, но вы можете вызвать локатор службы внутри цикла, разумеется), поэтому чаще всего каждая структура DI также предоставляет локатор службы.
Но в основном это все.
PS: То, что я описал здесь, - это метод, называемый инжекцией конструктора , также есть инъекция свойства, где не параметры конструктора, а свойства используются для определения и разрешения зависимостей. Думайте о внедрении свойства как о необязательной зависимости, а о внедрении конструктора как об обязательной зависимости. Но обсуждение этого вопроса выходит за рамки этого вопроса.