控制Kotlin DSL构建器中的范围

问题描述 投票:0回答:3

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

我有一些我无法改变的第三方课程:

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

class Card {
    var cardId = 0
}

我的目标是能够像这样构建一个Employee:

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
    }
}

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

错误:'内联乐趣Employee.addCard(ini​​t:(Scratch_1.Card)。() - > Unit):无法通过隐式接收器在此上下文中调用单元'。如有必要,请使用显式的

我理解错误的原因,我想考虑解决方案。

  1. 删除DSLMarker注释以便能够继承父作用域。不幸的是,这允许非法使用者: with(Builder) { val built = employee { id = 5 name = "max" addCard { employee { // ... } cardId = 5 } } }
  2. 使用限定条件访问父范围。但是接下来我们必须使用另一个合格的接收器。这非常冗长。 with(Builder) { val built = employee { id = 5 name = "max" with(this@with) { [email protected] { cardId = 5 } } } }
  3. 继承员工可以在其中放置扩展功能(这里不可能委托,因为我在Employee中有很多属性,并且没有全部由接口定义)。如果第三方课程是最终的,这并不总是有效。 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 } }

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

kotlin scope dsl builder
3个回答
1
投票
  1. 您可以定义extension function而不需要生成新类,它也适用于外来的不可触摸的源:
@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) }
  1. 有两种控制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
    }
  1. 您可能会发现这很有用:Kotlin DSL example

0
投票

我提供以下设计,它非常经典,结果代码很短。

  1. 由于Builder增加了额外的范围并阻止我们进口丑陋,我们只是停止使用with构造并重载invoke运算符。
  2. @DslMarker用于可编辑代码,将@Deprecated用于外部代码以控制范围
@Scoped
object builder {
    operator fun invoke(init: BuildingContext.() -> Unit) = BuildingContext().init()
}

@Scoped
class BuildingContext {
    fun employee(init: Employee.() -> Unit) = Employee().apply { init() }

    fun Employee.addCard(init: Card.() -> Unit) = run {card = Card().apply { init() }}

    @Deprecated(level = DeprecationLevel.ERROR, message = "Employee is forbidden here.")
    fun Employee.employee(init: (@Scoped Employee).() -> Unit) { }

    @Deprecated(level = DeprecationLevel.ERROR, message = "Card is forbidden here.")
    fun Card.addCard(init: (@Scoped Card).() -> Unit) { }
}

fun main(args: Array<String>) {
    builder {
        val crafted = employee {
            //employee {}  <-- deprecated, causes compilation error
            id = 5
            name = "max"
            addCard {
                // addCard {} <-- deprecated too
                cardId = 7
            }
        }
        println(crafted.card?.cardId)
    }
}

完整版本在这里工作:https://pl.kotl.in/ICLYZyetU


0
投票

好的,我想我现在有一个很好的概述。

首先,我认为问题的原因是构建器对象上的IScoped。删除它,它的工作原理。但它仍然允许“非法”语法:

val built = employe {
        id = 5
        name = "max"
        addCard {
            employe {

            }
            cardId = 5
        }
    }

解决方案

只保留构建器对象中的扩展方法,不要在后面添加注释。

就我而言,我必须引入另一个构建器来开始构建

object EmployeBuilder {   
}

object Builder {
    inline fun EmployeBuilder.employe(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
    }

 }

 fun main() {
    with(Builder) {
        val built = EmployeBuilder.employe {
            id = 5
            name = "max"
            addCard {
                cardId = 5
            }
        }
    }
}

现在我们拥有它:

  1. 没有对类上下文的“污染”,因为扩展方法仅在构建器对象中可用。
  2. 不能使用非法语法,因为所有参数都使用DslMarker注释锁定。
© www.soinside.com 2019 - 2024. All rights reserved.