控制Kotlin DSL生成器中的作用域

控制Kotlin DSL生成器中的作用域,kotlin,scope,dsl,builder,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" add

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

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

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
    }
}
不幸的是,现在我得到了一个臭名昭著的错误:

错误:“inline fun Employee.addCard(init:(Scratch_1.Card)。()->Unit):在此上下文中,隐式接收方无法调用Unit”。如有必要,请使用显式

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

  • 删除DSLMarker注释以能够继承父范围。不幸的是,这允许非法使用生成器:

    with(Builder) {
            val built = employee {
                id = 5
                name = "max"
                addCard {
                    employee {
                      // ...
                    }
                cardId = 5
            }
        }
    }   
    
  • 使用此限定项访问父作用域。但是我们必须使用另一个合格的接收器。这太冗长了

    with(Builder) {
            val built = employee {
                id = 5
                name = "max"
                with(this@with) {
                    this@employee.addCard {
                        cardId = 5
                    }
                }
            }
        }
    
  • 继承employee以便能够将扩展函数放入其中(这里不可能进行委派,因为我在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
        }
    }
    

  • 那么最好的解决方案是什么?我错过什么了吗? 非常感谢您阅读这篇文章

  • 您可以在不创建新类的情况下进行定义, 它也适用于外国不可接触的来源:
  • 有两种经典工具可以控制dsl范围:
    • @DSLMarker
      获取您正在使用的可实现代码,以及
    • @不推荐(level=ERROR)
      用于第一种方法不起作用的所有其他情况
  • 例如,当前可以构建嵌入式员工:

    val built = employee {
           id = 5
           name = "max"
           addCard {
               cardId = 6
           }
           employee {  }  // <--- compilable, but does not have sense
       }
    
    现在,以下示例不可编译:

     val built = employee {
            //...
            employee {  }  // <--- compilation error with your message
        }
    
    val-builded=employee{
    //...
    雇员{}//
    
  • 您可以在不创建新类的情况下进行定义, 它也适用于外国不可接触的来源:
  • 有两种经典工具可以控制dsl范围:
    • @DSLMarker
      获取您正在使用的可实现代码,以及
    • @不推荐(level=ERROR)
      用于第一种方法不起作用的所有其他情况
  • 例如,当前可以构建嵌入式员工:

    val built = employee {
           id = 5
           name = "max"
           addCard {
               cardId = 6
           }
           employee {  }  // <--- compilable, but does not have sense
       }
    
    现在,以下示例不可编译:

     val built = employee {
            //...
            employee {  }  // <--- compilation error with your message
        }
    
    val-builded=employee{
    //...
    
    employee{}/我将提供以下设计,它非常经典,生成的代码很短

  • 由于
    Builder
    增加了额外的作用域并阻止了丑陋的导入,我们只需通过
    构造停止使用它,并重载
    调用
    操作符即可

  • 对可编辑代码使用
    @DslMarker
    ,对外来代码使用
    @Deprecated
    ,以控制范围

  • @作用域
    对象生成器{
    运算符fun invoke(init:BuildingContext.(->Unit)=BuildingContext().init()
    }
    @范围
    类构建上下文{
    有趣的员工(init:employee.(->Unit)=employee()。应用{init()}
    fun Employee.addCard(init:Card.(->Unit)=运行{Card=Card()。应用{init()}
    @已弃用(level=DeprecationLevel.ERROR,message=“此处禁止员工”)
    fun Employee.Employee(init:(@Scoped Employee)。()->Unit){
    @已弃用(level=DeprecationLevel.ERROR,message=“此处禁止使用卡片”)
    有趣的卡片.addCard(init:(@Scoped Card)。()->Unit){
    }
    趣味主线(args:Array){
    建筑商{
    val=employee{
    
    //employee{}我将提供以下设计,它非常经典,并且生成的代码很短

  • 由于
    Builder
    增加了额外的作用域并阻止了丑陋的导入,我们只需通过
    构造停止使用它,并重载
    调用
    操作符即可

  • 对可编辑代码使用
    @DslMarker
    ,对外来代码使用
    @Deprecated
    ,以控制范围

  • @作用域
    对象生成器{
    运算符fun invoke(init:BuildingContext.(->Unit)=BuildingContext().init()
    }
    @范围
    类构建上下文{
    有趣的员工(init:employee.(->Unit)=employee()。应用{init()}
    fun Employee.addCard(init:Card.(->Unit)=运行{Card=Card()。应用{init()}
    @已弃用(level=DeprecationLevel.ERROR,message=“此处禁止员工”)
    fun Employee.Employee(init:(@Scoped Employee)。()->Unit){
    @已弃用(level=DeprecationLevel.ERROR,message=“此处禁止使用卡片”)
    有趣的卡片.addCard(init:(@Scoped Card)。()->Unit){
    }
    趣味主线(args:Array){
    建筑商{
    val=employee{
    
    //员工{}好的,我想我现在有了一个很好的概述

    首先,我认为问题的原因是构建器对象上的IScope。当删除它时,它工作正常。 但是,它仍然允许使用“非法”语法:

    解决方案

    只在生成器对象中保留扩展方法,不要在后者上添加注释

    就我而言,我必须介绍另一个建筑商来开始施工

    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
                }
            }
        }
    }
    
    现在我们有了它:

  • 不会“污染”类上下文,因为扩展方法仅在生成器对象中可用
  • 不能使用非法语法,因为所有参数都被DslMarker注释锁定

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

    首先,我认为问题的原因是构建器对象上的IScope。当删除它时,它工作正常。 但是,它仍然允许使用“非法”语法:

    解决方案

    只在生成器对象中保留扩展方法,不要在后者上添加注释

    就我而言,我必须介绍另一个建筑商来开始施工

    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
                }
            }
        }
    }
    
    现在我们有了它:

  • 没有类上下文的“污染”,因为扩展
    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
                }
            }
        }
    }