Хотя язык программирования Kotlin готовится к общедоступной программе раннего доступа, я хочу поделиться с вами одним примером того, как легко его можно использовать с существующими базами кода Java.
Эта короткая заметка не предназначена для того, чтобы научить читателя, как использовать Kotlin, но только для того, чтобы он / она заинтересовался обучением.
Мы создадим небольшое игрушечное приложение, используя Kotlin и очаровательный фреймворк Guice. Приложение не более чем обычное «Hello, World!» но он содержит службу приложений и службу регистрации, и каждая служба будет иметь два воплощения — для тестирования и для производственного использования.
Давайте начнем с абстрактного LoggingService. Это будет абстрактный класс только с одним абстрактным методом для вывода переданной строки. Скорее всего, приведенный ниже код не требует особых пояснений.
abstract class LoggingService() { abstract fun info(message: String?) }
Теперь мы определим простую реализацию, которая выводит данное сообщение в стандартный вывод
class StdoutLoggingService() : LoggingService(){ override fun info(message: String?) = println("INFO: $message") }
Модификатор override сообщает компилятору, что метод переопределяет некоторый метод суперкласса.
Обратите внимание также на строку интерполяции
Теперь мы создадим немного более сложную реализацию, которую мы будем использовать в производстве ?
class JdkLoggingService [Inject] (protected val jdkLogger: Logger) : LoggingService() { override fun info(message: String?) = jdkLogger.info(message) }
[Inject] аннотация на конструкторе сообщает Guice, как создать JdkLoggerService.
val в параметре конструктора просит создать конечное свойство, инициализированное данным значением. var запрашивает не окончательный, и ни один из них не определяет простой параметр конструктора
Теперь мы готовы определить абстрактный сервис приложения и обе его реализации для тестирования и производственного использования.
abstract class AppService(protected val logger: LoggingService, protected val mode: String) { fun run() { logger.info("Application started in '$mode' mode with ${logger.javaClass.getName()} logger") logger.info("Hello, World!") } } class RealAppService [Inject] (logger: LoggingService) : AppService(logger, "real") class TestAppService [Inject] (logger: LoggingService) : AppService(logger, "test")
Обратите внимание, как элегантно Kotlin позволяет нам определять свойства и передавать значения суперконструктору
Теперь перейдем к более интересной части — созданию min-DSL для Guice. Но давайте начнем с того, как мы хотим, чтобы наше приложение выглядело.
fun main(args: Array<String>) {
fun configureInjections(test: Boolean) = injector {
+module {
if(test) {
bind<LoggingService>().toInstance (StdoutLoggingService())
bind<AppService>().to<TestAppService> ()
}
else {
bind<LoggingService>().to<JdkLoggingService>()
bind<AppService>().to<RealAppService> ()
}
}
}
configureInjections(test=false).getInstance<AppService> ().run()
}
Здесь есть несколько вещей, на которые стоит обратить внимание
- Мы используем локальную функцию для настройки инъекций. В этом нет необходимости, но разрешите группировку кода.
- Возвращаемый тип функции configureInjection выводится из ее тела (вызов инжектора finction — корня нашего DSL, который мы вскоре определим)
- Мы вызываем configureInjection, используя синтаксис именованных аргументов. Это не обязательно в нашем случае, но очень удобно, когда у нас много параметров, и некоторые из них являются необязательными.
Теперь пришло время определить простые части нашего DSL. Это будет три функции расширения для некоторых классов Guice.
fun <T> Binder.bind() = bind(javaClass<T>()).sure() fun <T> AnnotatedBindingBuilder<in T>.to() = to(javaClass<T>()).sure() fun <T> Injector.getInstance() = getInstance(javaClass<T>())
- Пожалуйста, прочитайте о функциях расширения в документации Kotlin
- Заметка в дисперсии, используемая в функции «to». Пожалуйста, прочитайте о дженериках и отклонениях в документации Kotlin. Это действительно интересно
- Вызов javaClass <T> () — это стандартная библиотека Kotlin для Java (помните, что Kotlin можно скомпилировать и для других платформ, таких как javascript), эквивалентна T.class в Java. Огромная разница заключается в том, что в Kotlin он применим не только к реальным классам, но и к параметрам универсального типа (благодаря информации о типах во время выполнения).
- Вызов метода sure () является способом Kotlin (который вскоре будет заменен специальной языковой конструкцией), чтобы обеспечить ненулевое значение. Поскольку в Java нет обозначений обнуляемых и ненулевых типов, нам нужно позаботиться о точке интеграции. Пожалуйста, прочитайте удивительную историю нулевой безопасности в документации Kotlin.
Наконец мы готовы к самой сложной части — определению метода инжектора.
fun injector(config: ModuleCollector.() -> Any?) : Injector { val collector = ModuleCollector() collector.config() return Guice.createInjector(collector.collected).sure() }
У него есть один параметр типа ModuleCollector. () -> Любой? что означает «функцию расширения для ModuleCollector (мы определим ModuleCollector несколькими строками ниже), которая не принимает никаких параметров и возвращает значение любого типа, который может быть потенциально обнуляемым»
Реализация очень проста
- мы создаем ModuleCollector
- мы используем его как приемник для вызова данной функции конфигурации
- мы просим Guice создать Injector для всех настроенных модулей ( подробности см. в документации Guice )
Это чрезвычайно мощный метод определения DSL в Kotlin
Как мы видели выше, мы вызываем метод инжектор с выражением функционального блока.
Тот факт, что параметр является функцией расширения, делает все (по модулю правил видимости) методы ModuleCollector доступными внутри функционального блока.
Последняя часть нашего DSL — это определение ModuleCollector.
Содержит внутренний список собранных модулей и только два метода
- метод module — использует вариацию одного и того же трюка «функция-расширение-параметр» — мы используем функцию расширения для реализации метода анонимного класса.
Пожалуйста, обратитесь к документации Kotlin по объектным выражениям
- метод плюс — переопределяет унарную операцию плюс
Пожалуйста, обратитесь к документации Kotlin по перегрузке оператора .
Очень важно отметить, что этот метод является и методом расширения, и членом класса ModuleCollector. Это позволяет очень хорошо ловить контекст на сайте вызова. В нашем случае выше мы вызываем этот метод внутри функции расширения с приемником ModuleCollectoror и применяем его к модулю, созданному путем вызова метода Module.
class ModuleCollector() { val collected = ArrayList<Module> () fun module (config: Binder.()->Any?) : Module = object: Module { override fun configure(binder: Binder?) { binder.sure().config() } } fun Module.plus() { collected.add(this) } }
Мы сделали. Я надеюсь, что это было интересно. Эта заметка является очень кратким и не подробным введением о вкусностях Kotlin. Если это побудило вас прочитать больше о Kotlin, тогда моя цель достигнута.
Спасибо и до следующего раза!