DI vs Service Locator - makstron/info GitHub Wiki

DI

Прежде всего, что такое Dependency Injection? Простыми словами, это когда объект принимает зависимости извне, а не создаёт или добывает их сам. Приведу пример. Предположим, у нас есть интерфейс Engine, его реализация, а также класс Car, который зависит от Engine. Без использования DI это может выглядеть вот так

interface Engine
class DefaultEngine: Engine

class Car {
  private val engine: Engine = DefaultEngine()
}

fun main() {
  val car = Car()
}

Если переписать класс Car с использованием подхода DI, то может получиться вот это:

class Car(private val engine: Engine)

fun main() {
  val car = Car(DefaultEngine())
}

Всё просто – класс Car не знает, откуда приходит реализация Engine, при этом заменить эту самую реализацию легко, достаточно передать её в конструктор.

Service Locator

Попробуем разобраться с Service Locator. Тут тоже ничего сложного – это некий реестр, который по запросу может предоставить нужный объект.

object ServiceLocator {
  fun <reified T> register(factory: () -> T) { ... }
  fun <reified T> resolve(): T { ... }
}

У нас есть возможность добавить в наш реестр некую зависимость, а также получить эту зависимость. Вот пример использования с нашими двигателями и машинами:

interface Engine
class DefaultEngine: Engine

class Car {
  private val engine: Engine = ServiceLocator.resolve()
}

fun main() {
  ServiceLocator.register<Engine> { DefaultEngine() }
  val car = Car()
}

Это отдалённо похоже на DI, ведь класс Car получает зависимость извне, не зная о реализации, но у данного подхода есть проблема – мы ничего не знаем о зависимостях класса Car, есть ли они вообще. Можно попробовать такой подход:

interface ServiceLocator {
  fun <reified T> register(factory: () -> T)
  fun <reified T> resolve(): T
}

class DefaultServiceLocator: ServiceLocator { ... }

class Car(private val serviceLocator: ServiceLocator) {
  private val engine = serviceLocator.resolve<Engine>()
}

fun main() {
  val serviceLocator = DefaultServiceLocator()
  serviceLocator.register<Engine> { DefaultEngine() }
  val car = Car(serviceLocator)
}

Теперь мы знаем, что у Car есть зависимости, но всё ещё не знаем какие. Т.е. это не решение нашей проблемы. Но есть ещё один вариант:

class Car(private val engine: Engine)

fun main() {
  ServiceLocator.register<Engine> { DefaultEngine() }
  val car = Car(ServiceLocator.resolve<Engine>())
}

Sources

⚠️ **GitHub.com Fallback** ⚠️