前言
在食用本文前你需要了解 Kotlin语言,及用到的特性——带接收者的 lambda、内联 reified 泛型函数、扩展函数、中缀函数等。当然这些大部分都是在 Kotlin 中想要写出 DSL 语言结构的必要知识。
DSL 构造命令
关于注册命令的写法大部分都是远古判断法或者注解反射法。关于注解反射这个东西,虽然外表看起来简洁美观,但背后十分邪恶,我个人也非常不喜欢。因此我们从 CommandMap 注册命令开始入手。
首先需要拿到 CommandMap,它位于 Server 类中。
1 | fun getCommandMap(): CommandMap = |
此处没有对非空做出处理,因为我们知道 Server 类中肯定是存在这个东西的。之后我们就要把抽象类 Command 给实现一下他的 execute 方法。在构造 DSL 过程中,我们把这个方法中执行的内容封装成一个 lambda (CommandSender, String, Array<out String>) -> Unit,用 result 作为最终返回结果。我们定义一个类继承 Command ,并封装上下文及 execute 函数。
1 | class PackingCommand |
name、description、usageMessage、aliases对应 plugin.yml 下对命令的配置;action会在execute被调用时执行result对应处理命令时返回的布尔结果(这里在 runtime 前已经固定)
定义完 PackingCommand 后,可以实现其 DSL Builder。代码如下:
1 | class CommandBuilderDsl(val name: String) { |
写完 CommandBuilderDsl 后还需要提供一另一个 CommandScope 来构造 Builder,在这之前构造好的 Command 需要存起来,在插件启用时调用,这部分不贴代码了,就是一个 MutableList<PackingCommand>,向外提供注册方法。CommandScope 的代码如下:
1 | class CommandScopeDsl { |
目前 CommandScope 中就一个函数,也可以省略 CommandScope,直接将 command() 改为顶层函数,不过未来肯定还会加东西,而且这样写是不合理的。贴一下前面的注册方法,commands 是那个持有 PackingCommand 的 list:
1 | fun register(commandMap: CommandMap) { |
再加上把 CommandScope 开放出来的函数:
1 | fun buildCommands(block: CommandScope.() -> Unit) { |
至此就完成了,在插件启用时调用 CommandHolder.register(getCommandMap()) 注册就好。
随便写个命令举个例子:
1 | buildCommands { |
总结
有几个问题:
- 命令多了看起来很乱,大括号越写越多
- 封装性欠缺
- 无法美观处理子命令
除了第一个问题是 Kotlin DSL 的硬伤外,其余都可以自己慢慢优化、实现。emerald 主要思想来源于本文,可以在 这里 找到我进一步封装的代码。