Введение
Волею случая и некоторых немалых усилий ушел со старого ужасного места работы и джавы и пришел на новое прекрасное(пока) с котлин. Очень сахарный язык. А все мы любим сладенькое. Очень понравилась идея котлин дсл и захотелось самому освоить и написать что-то простенькое.
Изначально подумал сделать дсл для телеграмм бота, но мне не хотелось копаться с их апи, да и дсл уже такой существует. Пусть эта заготовка будет некоторой тренировкой ну и может шпаргалкой на будущее.
Что мы сегодня напишем Брейн?

Все будет просто. Будем обрабатывать текстовые команды в зависимости от состояния, по-разному. Пусть нам приходит id по которому будем доставать состояние и команда. Ну и немного котлиновского сахара, в целом это не обязательно, но так выглядит красивше. Дженерик в этом классе — перечисление всех возможных состояний.
class Processor<E : Enum<E>>( private val stateProviderService: StateProviderService<E>, private val stateService: StateService<E> ) { fun process(action: String, identifier: Any): Set<String> { return stateService.getState(identifier) .provide(action) .nextState(identifier) .getActions() } private fun E.provide(action: String): E { return stateProviderService.provide(this, action) } private fun E.nextState(identifier: Any): E { return stateService.nextState(this, identifier) } private fun E.getActions(): Set<String> { return stateProviderService.getActions(this) } }
StateService — это интерфейс и его реализация не особо интересна, да и StateProviderService довольно прост. Map состояний против обработчиков команд, а в нем Map команд против действия.
class StateProviderService<E : Enum<E>> ( private val stateProviders: Map<E, ActionProviderService<E>> ) { fun provide(state: E, action: String): E { return stateProviders[state] ?.provide(action) ?: throw Exception("StateProvider must provide all states") } fun getActions(state: E): Set<String> { return stateProviders[state]?.actionProviders?.keys ?: throw IllegalArgumentException("State $state has no actions") } } class ActionProviderService<E : Enum<E>>( val actionProviders: Map<String, () -> E>, val defaultProvider: (() -> E)? ) { fun provide(action: String): E { return actionProviders[action] ?.invoke() ?: defaultProvider?.invoke() ?: throw Exception("ActionProvider must provide all actions") } }
Проблема теперь — заполнить все эти мапы.
Да будет билдер

На данный момент чтобы собрать провайдер с состояниями A и B и у каждого по одной команде. Я начал было это заполнять, но эта настройка внезапно вылезла за края экрана и я бросил это дело.
В котлине можно вызывать функцию apply() она дает возможность заполнить все что нужно. Проблема в том, что прийдется открыть все поля и сделать пустой конструктор. Тогда бы это выглядело примерно так:
Processor().apply { stateProviderService = StateProviderService(...) stateService = StateServiceImpl() }
Пусть для каждого сервиса у нас будет по билдеру. В них добавим всякие вспомогательные функции.
class StateProviderBuilder<E : Enum<E>> { private val stateProviders: MutableMap<E, ActionProviderService<E>> = mutableMapOf() fun toService() = StateProviderService(stateProviders) fun state(state: E, builder: ActionProviderBuilder<E>.() -> Unit) = stateProviders.put(state, ActionProviderBuilder<E>().apply(builder).toService()) } class ActionProviderBuilder<E : Enum<E>> { private val actionProviders: MutableMap<String, () -> E> = mutableMapOf() private var defaultProvider: (() -> E)? = null fun toService() = ActionProviderService(actionProviders, defaultProvider) fun action(action: String, body: () -> E) = actionProviders.put(action, body) fun default(body: () -> E) { defaultProvider = body } }
В котлине если лямбда — последний аргумент, ее можно вынести за скобки. Например функция state:
state(TestState.A) { action("go-to-B") { println("i'm mooving to B") TestState.B } }
Вааааа как красиво. Теперь просто соберем Processor и плов готов.
fun <E : Enum<E>> processor(stateService: StateService<E>, builder: StateProviderBuilder<E>.() -> Unit): Processor<E> = Processor(StateProviderBuilder<E>().apply(builder).toService(), stateService)
Во первых это красиво
Теперь добавим чутка сахара. Есть такая штука infix функции, они могут использоваться как операторы. Допустим теперь я хочу писать «print» doing {…}
infix fun String.doing(action: () -> E) { action(this, action) }
Ну и появилась одна заковыка, допустим я могу перейти из разных состояний одним способом. Для этого нам нужно собрать состояния и действие и передать их дальше. Как-то так это listOf(TestState.A, TestState.B) withAction «take the world» doing
infix fun List<E>.withAction(actionName: String) = ActionContainer(listOf(this), listOf(actionName)) infix fun ActionContainer<E>.doing(action: () -> E) = addActionWithActionContainer(this, action) private fun addActionWithActionContainer(actionContainer: ActionContainer<E>, action: () -> E) { actionContainer.states.forEach { it state { actionContainer.actionNames.doing(action) } } } class ActionContainer<E : Enum<E>>( val states: List<E>, val actionNames: String )
Чему мы научились Палмер

Тут нет ничего нового, к сожалению, но возможности котлина мне нравятся. Смотрим изучаем. Надеюсь вам понравилось.
Вот репозитрий
Вот телеграмм (я там пропал надолго, но уже вроде как вернулся)
ссылка на оригинал статьи https://habr.com/ru/post/704168/
Добавить комментарий