Koin – это Dependency Injection или Service Locator?

от автора

Введение

В Android-разработке для DI традиционно используют Dagger 2, очень мощный фреймворк с кодогенерацией. Но есть проблема: новичкам сложно его использовать. Сами принципы DI просты и понятны, но Dagger усложняет их. Можно жаловаться на поголовное падение грамотности программистов, но от этого проблема не исчезнет.

С появлением Kotlin появилась возможность писать удобные вещи, которые были бы практически невозможны с использованием Java. Одной из таких вещей стал Koin, который является не DI, а Service Locator, который многими трактуется как anti-pattern, из-за чего многие принципиально его не используют. А зря, ведь у него очень лаконичный API, упрощающий написание и поддержку кода.

В данной статье я хочу помочь новичкам разобраться с разграничением понятий Dependency Injection и Service Locator, но не с самим Koin.

Dependency Injection

Прежде всего, что такое 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. Тут тоже ничего сложного – это некий реестр, который по запросу может предоставить нужный объект. Пока я предлагаю отойти в сторону от Koin и представить некий абстрактный ServiceLocator, без деталей реализации, но с простым и понятным API.

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>()) }

Это и есть Dependency Injection в чистом виде. С Koin это бы выглядело вот так:

interface Engine class DefaultEngine: Engine class Car(private val engine: Engine)  fun carModule() = module {   factory<Engine> { DefaultEngine() }   factory { Car(get<Engine>()) } }  fun main() {   val koin = startKoin {     modules(carModule())   }.koin    val car = koin.get<Car>() }

Увы, нам всё ещё необходимо обращаться к Koin для получения зависимостей, но это никоим образом не противоречит принципам Dependency Injection.

ссылка на оригинал статьи https://habr.com/ru/post/488072/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *