Введение
В 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/
Добавить комментарий