我学习 kotlin DSL 并尝试描述我的领域对象。例如,我在 github ( VillageDSL ) 上阅读了非常简单的示例,发现它们非常有用:
val v = village {
house {
person {
name = "Emily"
age = 31
}
person(name = "Hannah") {
age = 27
}
person("Alex", 21)
person(age = 17, name = "Daniel")
}
}
或者
val v = village containing houses {
house with people {
"Emily" age 31
"Hannah" age 27
"Alex" age 21
"Daniel" age 17
}
}
和互联网上的其他例子。
所有这些示例都展示了如何在单个空间中描述单个域。但真正的域更复杂,它们之间有联系。我希望 DSL 用户能够通过自动完成、验证等从另一个 DSL 引用对象。
I. 例如,我有字典:
val books = books {
book {
isbn = "0321712943"
name = "Domain-Specific Languages"
}
book {
isbn = "9781680507935"
name = "Programming DSLs in Kotlin"
}
}
我想参考这些书:
val shelf = shelf {
book("9781680507935")
book("0321712943")
}
我可以在运行时进行这样的验证:
data class Shelf(val books: List<Book> = listOf()): Populatable {
override fun populate(bookDictionary: Books) {
val dictionaryBooks = bookDictionary.books
books.forEach {
val book = checkNotNull(dictionaryBooks[it.isbn]) {
"ISBN [${it.isbn}] was not found in book dictionary"
}
it.name = book.name
}
}
}
整个模型将保持一致,但在设计时用户必须手动输入 ISBN ( $.shelf.book.isbn
)。
二、另一种变体。我在字典中这样描述每一本书:
val book0321712943 = book {
isbn = "0321712943"
name = "Domain-Specific Languages"
}
val book9781680507935 = book {
isbn = "9781680507935"
name = "Programming DSLs in Kotlin"
}
}
这个变体对管理这本词典的用户不太友好,但对参考这些书的用户更友好:
val shelf = shelf {
book(Books.book9781680507935)
book(Books.book0321712943)
}
可以在设计时使用自动完成和验证工作。
三、我可以想象一些组合变体:字典像变体“I”($.books.book
)一样管理,然后是代码生成的结构,如变体“II”(val book0321712943 = ... val book9781680507935 ...
)。这种代码生成也是手动/中间操作,它不允许在没有项目构建等情况下立即使用字典实体。
所有这些变体都有明显的缺点......
所以我的问题是关于描述相关 DSL 的最佳实用方法和模板。