将块从一个 class 传递到另一个时,如何访问 Kotlin 中的私有成员

How can I access private members in Kotlin when passing a block from one class to another

我有一个名为 ClassA 的 class,我希望它完全私有,除了一个可以接收代码块并执行它的方法。我希望传递给此方法的代码块能够调用 ClassA 的私有成员。这是我现在所拥有内容的高度简化表示:

class ClassA() {  
    private fun printFoo(session: SessionInfo){
        System.out.println("Foo")
    }

    fun runBlock(block: ClassA.() -> Unit) {
        this.block()
    }
}

class ClassB(var classA: ClassA) {    
    fun doThing() {
            classA.runBlock { this.printFoo() }
    }
}

以上代码无法编译,因为 printFoo 只是 ClassA 中的私有访问。如果我更改它对 public 的访问权限,则此代码可以正常工作。有没有一种方法可以在我从 ClassB 传入的块中引用此 printFoo 而不必创建方法 public?

有两种方法可以解决这个问题:使用反射或代理访问器。

使用反射

反思几乎可以让你做任何你喜欢做的事。你总是可以用它强制通过密封:

class HasPrivateMembers(private val foo: String)

fun main() {
    val x = HasPrivateMembers("bar")
//    println(x.foo) // doesn't work due to private specifier
    val foo = x.javaClass.getDeclaredField("foo").run {
        isAccessible = true
        get(x) as String
    }.also {
        println(it)
    }
}

虽然我不会采用这种方法。感觉有点老套,应该是这样。如果您认可这种方法,您就不会设计对您的字段的架构访问。你基本上是在表明不应该访问你的字段,但是如果有人希望直接与他们交互(甚至违反不变量,如果有的话),他们可以自由使用可以做到这一点的工具。

这导致我们采用第二种方法。

使用代理访问器对象

想法是创建一个 private inner class,用于 委托 调用它与 ClassA 共享的 API :

class ClassA {
    private var privateField = "this is private"

    private fun printFoo(){
        println("Foo")
    }

    interface Accessor {
        fun printFoo()
        var privateField: String
    }

    private inner class AccessorImpl : Accessor {
        override fun printFoo() {
            this@ClassA.printFoo()
        }

        override var privateField: String
            get() = this@ClassA.privateField
            set(value) {
                this@ClassA.privateField = value
            }
    }

    fun runBlock(block: Accessor.() -> Unit) {
        AccessorImpl().block()
    }
}

class ClassB(var classA: ClassA) {
    fun doThing() {
        classA.runBlock {
            printFoo()
            privateField = "but I just changed it"
        }
    }
}

这里发生了很多变化,所以让我解释每一个变化以使代码片段有意义:

  • private var privateField = "this is private" - 我添加了一个额外的 private 字段来证明上述解决方案也适用于数据字段。

  • interface Accessor——这是核心思想。我们有一个 public interface,它充当外部世界和委托私有字段访问的私有实现之间的粘合剂。

  • private inner class AccessorImpl - 这是核心思想的实现。

    • 它是 private,因此没有人能够利用它,唯一可以使用它的方法是通过您提供的 runBlock()

    • 它是一个 inner class,因此它的对象持有对适当 ClassA 实例的引用(因此是 this@ClassA 部分)。

    • 它与您的 ClassA 具有相同的字段,但 public

    • 它所做的一切都是为了将​​ 每个操作 委托给外部 class' 对象。

  • fun runBlock(block: Accessor.() -> Unit) - 这是 public API 用于访问所有 private 字段。请注意,接收器是 Accessor,而不是 AccessorImpl。不可能是后者,因为它是 private(出于上述原因)。

    • AccessorImpl().block() - 我们需要创建一个 Accessorblock 调用。我们需要一个能够与 ClassAprivate 字段交互的工具。因此我们创建 AccessorImpl 并在其上调用 block()。请记住 AccessorImpl,由于是 inner class 知道 它绑定到调用 runBlock() 的对象。

虽然我 强烈 更喜欢第二种解决方案而不是第一种解决方案,但最好记住在正常情况下不应违反密封性。每次你更改 private API of ClassA(这......听起来很奇怪,但这就是我们在这里处理的),你将不得不同时更改 AccessorAccessorImpl 来反映这一点。没有简单的解决方法,坦率地说,我认为这是一件好事。处理此类架构时应格外小心。