Каталог статей
внедрение зависимости
1. Введение – зачем необходимо внедрение зависимостей
1.1 Сделать кофемашину
У нас есть кофемашина (CoffeeMaker
), которая состоит из помпы (Pump
) и нагревателя (Heater
).
Процесс приготовления кофе выглядит следующим образом:
Во-первых, код обогревателя следующий:
class ElectricHeater{
private var heating = false
fun isHot(): Boolean {
return heating
}
fun off() {
heating = false
}
fun on() {
println("~ ~ ~ heating ~ ~ ~")
heating = true
}
}
Далее идет насос, который представляет собой сифонное устройство, причем устройство имеет противодурацкий механизм, который не дает тупым людям набирать воду, если они забудут ее нагреть:
class Thermosiphon(val heater: ElectricHeater){
fun pump() {
if(heater.isHot()){
println("=> => pumping => =>")
}
}
}
Наконец, комбинация насоса и нагревателя дает кофемашину:
class Coffeemaker{
private val heater = ElectricHeater()
private val pump = Thermosiphon(heater)
fun brew(){
heater.on()
pump.pump()
println(" [_]P coffee! [_]P ")
heater.off()
}
}
1.2 Зависимости
Из приведенной вышеUML
диаграммы классов мы можем знать, что кофемашина зависит от нагревателя и насоса, а насос зависит от нагревателя. Нагреватель и кофемашина могут работать независимо без помпы, но кофемашина и помпа неотделимы от нагревателя. Таким образом, модульные кофемашины высокого уровня полагаются на модульные насосы и нагреватели низкого уровня.
Давайте еще раз взглянем на Coffeemaker
этот класс. Когда нам нужна кофемашина, нам нужно одновременно создать новый насос и нагреватель. Таким образом, насос, нагреватель и кофемашина тесно связаны между собой. Какие проблемы это вызовет?
- Вопрос 1: Любитель, который коллекционирует кофемашины, хочет иметь несколько кофемашин, но не хочет иметь много помп и нагревателей. В этом случае, поскольку каждая кофемашина оснащена собственным насосом и нагревателем, это вызовет проблему. Отходы ресурсов.
- Вопрос 2: Когда производитель разрабатывает кофемашину, он хочет опробовать поддельный нагреватель и насос, чтобы проверить, нет ли каких-либо ошибок в процессе работы кофемашины. В данном случае сделать это без перемещения
Coffeemaker
рецепта (кода) невозможно. - Вопрос 3: Однажды нагреватель был улучшен, и метод
on
был измененopen
. Кофемашина должна соответствовать нагревателю и соответствовать требованиям доработка, чтобы быть успешной. .
1.3 Улучшить технологию производства
Хорошо, тогда давайте улучшим процесс производства кофемашины и решим эти проблемы.
1.3.1 Разблокировка зависимостей
Чтобы сначала решить первую проблему, просто настройте производственную линию: отдайте на аутсорсинг производство помп и нагревателей и разберите кофемашину на модули.
Улучшенная формула выглядит следующим образом:
class Coffeemaker(
private val heater:ElectricHeater,
private val pump:Thermosiphon
) {
fun brew() {
heater.on()
pump.pump()
println(" [_]P coffee! [_]P ")
heater.off()
}
}
Отлично, теперь коллекционеры кофемашин могут иметь только один нагреватель и насос, но много кофемашин.
class TheManHavingManyCoffeemakers {
val heater = ElectricHeater()
val pump = Thermosiphon(heater)
val cofeemaker1 = Coffeemaker(heater,pump)
val cofeemaker2 = Coffeemaker(heater,pump)
...
}
1.3.2 Инверсия зависимостей
Далее решите второй, третий и четвертый вопросы. Производитель кофемашины немного подумал и решил указать стандартные правила (interface
), чтобы указать характеристики нагревателя и насоса, например, какие функции они должны иметь и т. д. Как конкретно реализовать эту функцию, не входит в стандарт. Поэтому кофемашины и другие компоненты должны быть спроектированы в соответствии с этим стандартом.
interface Heater{
fun on()
fun off()
fun isHot():Boolean
}
interface Pump{
fun pump()
}
Рецепт кофемашины изменяется на следующий процесс:
class Coffeemaker(
private val heater: Heater,
private val pump: Pump
){
fun brew(){
heater.on()
pump.pump()
println(" [_]P coffee! [_]P ")
heater.off()
}
}
Здесь все внешние детали должны соответствовать спецификациям (interface
), прежде чем их можно будет приобрести для производства кофемашины, чтобы любой производитель мог производить помпы и нагреватели и использовать их в производстве. кофемашин, не нужно беспокоиться о ElectricHeater
или других нагревателях, например, его не проблема заменить на: MagicHeater
.
class MagicHeater : Heater {
private var heating = false
override fun isHot(): Boolean {
return heating
}
override fun off() {
heating = false
}
override fun on() {
println("~ ~ ~ This is Magic ~ ~ ~")
println("~ ~ ~ heating ~ ~ ~")
println("~ ~ ~ magic ~ ~ ~")
heating = true
}
}
Но если он не соответствует спецификациям, замена on
на open
будет исключена из индустрии кофемашин, поскольку это не соответствует рыночным правилам. . Первоначально кофемашины должны были проектироваться с использованием модулей низкого уровня, но теперь как модули высокого, так и низкого уровня должны проектироваться с использованиемinterface
. В разработке программного обеспечения этот подход называется инверсией зависимостей и так далее.
1.3.3 Улучшить технологию и снизить затраты
Для производства кофемашины сейчас необходимы как минимум три цепочки поставок: производство кофемашин, производство насосов и производство нагревателей. То есть каждый раз, когда кофемашина производится в другом месте, мы также должны изготовить хотя бы один комплект нагревателей и помп.
class PeopleInBeijing {
val heater = ElectricHeater()
val pump = Thermosiphon(heater)
val cofeemaker = Coffeemaker(heater,pump)
...
}
class PeopleInShanghai {
val heater = ElectricHeater()
val pump = Thermosiphon(heater)
val cofeemaker = Coffeemaker(heater,pump)
...
}
...
Это более хлопотно и требует написания повторяющегося кода, что эквивалентно созданию этих продуктов вручную. Что еще более неприятно, так это то, что нагреватель и насос находятся снаружи, и пользователи могут получить к ним доступ в любое время и использовать их для чего угодно, например, для глажки одежды или даже людей. Представляем партию специализированных машин (Injector/Container...
), которые отвечают за производство и установку нагревателей и помп, необходимых для кофемашин.
class Injector{
private val heater = ElectricHeater()
private val pump = Thermosiphon(heater)
fun provideHeater() : Heater{
return heater
}
fun providePump() : Pump{
return pump
}
}
class Coffeemaker(injector:Injector)
{
private val heater = injector.provideHeater()
private val pump = injector.providePump()
fun brew(){
heater.on()
pump.pump()
println(" [_]P coffee! [_]P ")
heater.off()
}
}
Когда зависимые детали кофемашины необходимо заменить на детали других производителей или поддельные детали для проверки, достаточно заменить ихInjector
. Чтобы пойти еще дальше, машина может сформулировать правила (Интерфейс). Injector
class CoffeeMakerProvider{
companion object{
fun provideCoffeeMaker() = Coffeemaker(Injector())
}
}
Таким образом, вы можете получить новую кофемашину в любое время и в любом месте, а внутренние компоненты также упакованы и не могут быть показаны другим. Его также можно реализовать с использованием одного экземпляра, так что нагреватель и насос, поставляемые каждый раз, представляют собой одну и ту же машину, что обеспечивает повторное использование и экономию ресурсов. Injector
1.3.4 Внедрение технологий и сокращение затрат
Мы изготовили машинуInjector
раньше, что улучшило производственный процесс и снизило стоимость. Иногда, несмотря на то, что мы оптимизировали лучше, можно внедрить некоторые более эффективные процессы, такие как платформа внедрения зависимостей. В среде, где используется :Dagger
Kotlin
dependencies {
...
implementation 'com.google.dagger:dagger:2.17'
kapt 'com.google.dagger:dagger-compiler:2.17'
...
}
-
Внедрить. Сначала мы модифицируем помпу и кофемашину, требующие впрыска, и отмечаем объекты (нагреватели), которые необходимо впрыскивать,
@Inject
. ВDagger2
@Inject
можно использовать для обозначения конструкторов, свойств и функций.class Thermosiphon @Inject constructor(val heater: Heater) : Pump { override fun pump() { if(heater.isHot()){ println("=> => pumping => =>") } } } class CoffeeMaker @Inject constructor(private val heater: Heater, private val pump: Pump) { fun brew(){ heater.on() pump.pump() println(" [_]P coffee! [_]P ") heater.off() } }
-
Модуль. Затем создайте
Module
, его роль эквивалентна предыдущей ролиInjector
, отвечающей за производство деталей. ПосколькуThermosiphon
также зависит отHeater
, насос снабжен не@Provides
, а@Binds
Link метод предоставления насосу выбранного нами класса насоса (Thermosiphon
). Наконец, используйтеincludes
для соединения двух модулей.Dagger
одновременно подаст один и тот же нагреватель в кофемашину и накачку.@Module(includes = [PumpModule::class]) class CoffeeMakerModule { @Provides fun provideHeater():Heater{ return ElectricHeater() } } @Module abstract class PumpModule{ @Binds abstract fun providePump(pump: Thermosiphon):Pump }
-
Компонент. Наконец, есть линия по производству кофемашин
Component
, которая очень похожа на предыдущуюCoffeeMakerProvider
. Все, что нам нужно сделать, это указатьDagger
какой модуль использовать и какой тип кофемашины производить.@Component(modules = [CoffeeMakerModule::class]) interface CoffeeComponent { fun provideCoffeeMaker() : CoffeeMaker }
-
производит кофемашины. После того, как все будет готово,
build
этот товар.Dagger2
будет производить для нас компоненты и продукты на основе этих аннотаций.fun main(){ val coffeeProvider = DaggerCoffeeComponent.builder().build() val coffeeMaker = coffeeProvider.provideCoffeeMaker() coffeeMaker.brew() }
Ссылки:
История кофе: внедрение зависимостей, Dagger, Android