kotlin - 控制 Kotlin DSL 构建器中的范围

标签 kotlin scope dsl builder

我试图找到解决范围问题的完美解决方案,我真的很想听听您的意见。

我有一些无法更改的第三方类:

class Employee {
    var id = 0
    var name = ""
    var card : Card? = null
    // ...
}

class Card {
    var cardId = 0
}

我的目标是能够培养这样的员工:

val built = employee {
     id = 5
     name = "max"
     addCard {
          cardId = 5
     }
}

原始bean中没有方法addCard。 因此我想出了以下构建器:

@DslMarker
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class Scoped

@Scoped
object Builder {
    inline fun employee (init: (@Scoped Employee).() -> Unit): Employee {
        val e = Employee()
        e.init()
        return e
    }

    inline fun Employee.addCard(init: (@Scoped Card).() -> Unit) {
        val c = Card()
        c.init()
        card = c
    }
}

不幸的是,现在我遇到了臭名昭著的错误:

error: 'inline fun Employee.addCard(init: (Scratch_1.Card).() -> Unit): Unit' can't be called in this context by implicit receiver. Use the explicit one if necessary

我了解错误的原因,并且想考虑解决方案。

  1. 删除 DSLMarker 注释以便能够继承父范围。不幸的是,这允许非法构建者使用:

    with(Builder) {
            val built = employee {
                id = 5
                name = "max"
                addCard {
                    employee {
                      // ...
                    }
                cardId = 5
            }
        }
    }   
    
  2. 使用限定的 this 来访问父作用域。但随后我们必须使用另一个合格的接收器来获得合适的接收器。这相当冗长。

    with(Builder) {
            val built = employee {
                id = 5
                name = "max"
                with(this@with) {
                    <a href="https://stackoverflow.com/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="d8acb0b1ab98bdb5a8b4b7a1bdbdf6b9bcbc9bb9aabc" rel="noreferrer noopener nofollow">[email protected]</a> {
                        cardId = 5
                    }
                }
            }
        }
    
  3. 继承employee,以便能够将扩展函数放入其中(这里不可能进行委托(delegate),因为我在Employee中有很多属性,并且并非所有属性都由接口(interface)定义)。 如果第三方类是最终的,这并不总是有效。

    class EmployeeEx : Employee() {
        inline fun addCard(init: (@Scoped Card).() -> Unit) {
            val c = Card()
            c.init()
            card = c
       }
    }      
    

    和构建者:

    @Scoped
    object Builder {
        inline fun employee (init: (@Scoped EmployeeEx).() -> Unit): Employee {
            val e = EmployeeEx()
            e.init()
            return e
        }
    }
    

那么最好的解决方案是什么?我错过了什么吗? 非常感谢您阅读所有这些!

最佳答案

  1. 您可以定义extension function不产生新类(Class), 它也适用于外国不可触及的来源:
@DslMarker
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
annotation class Scoped

@Scoped
object Builder {
    inline fun employee(init: (@Scoped Employee).() -> Unit) = Employee().apply(init)
}

fun Employee.addCard(init: (@Scoped Card).() -> Unit) = run { card = Card().apply(init) }
  • 有两种经典工具可以控制 dsl 范围:
    • @DSLMarker 用于您正在使用的可实现代码,以及
    • @Deprecated (level = ERROR) 对于第一种方法不起作用的所有其他情况。
  • 例如,目前您可以构建嵌入式员工:

    val built = employee {
           id = 5
           name = "max"
           addCard {
               cardId = 6
           }
           employee {  }  // <--- compilable, but does not have sense
       }
    

    并且您可以通过弃用的方式直接禁止此操作:

    @Deprecated(level = DeprecationLevel.ERROR, message = "No subcontractors, please.")
    fun Employee.employee(init: (@Scoped Employee).() -> Unit) = Unit
    

    现在以下示例无法编译:

     val built = employee {
            //...
            employee {  }  // <--- compilation error with your message
        }
    
  • 您可能会发现这很有用:Kotlin DSL example
  • 关于kotlin - 控制 Kotlin DSL 构建器中的范围,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55684541/

    相关文章:

    class - BodyKt $ main $ Person @ 35cabb2a是什么?我可以得到它的属性吗?

    android - 如何使用 Anko 和 Kotlin 获取 SQLite 表的最大 _id 值?

    android - 使用 Gradle Kotlin DSL 在 settings.gradle.kts 中设置 gradle.ext

    ruby-on-rails - 不明确的列名 : error not working with tags

    namespaces - 上下文和命名空间有什么区别?

    unit-testing - 为什么 NUnit 没有 IsElementOf/IsOneOf 约束?

    java - 为 Xtext 语法创建一个自己的解释器

    java - Kotlin + Spring Boot请求编码

    Javascript:如何在立即调用创建对象时访问公共(public)成员变量

    tomcat - docker tomcat重新部署应用程序