将块从一个 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()
- 我们需要创建一个 Accessor
供 block
调用。我们需要一个能够与 ClassA
的 private
字段交互的工具。因此我们创建 AccessorImpl
并在其上调用 block()
。请记住 AccessorImpl
,由于是 inner class
, 知道 它绑定到调用 runBlock()
的对象。
虽然我 强烈 更喜欢第二种解决方案而不是第一种解决方案,但最好记住在正常情况下不应违反密封性。每次你更改 private API of ClassA
(这......听起来很奇怪,但这就是我们在这里处理的),你将不得不同时更改 Accessor
和 AccessorImpl
来反映这一点。没有简单的解决方法,坦率地说,我认为这是一件好事。处理此类架构时应格外小心。
我有一个名为 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()
- 我们需要创建一个Accessor
供block
调用。我们需要一个能够与ClassA
的private
字段交互的工具。因此我们创建AccessorImpl
并在其上调用block()
。请记住AccessorImpl
,由于是inner class
, 知道 它绑定到调用runBlock()
的对象。
虽然我 强烈 更喜欢第二种解决方案而不是第一种解决方案,但最好记住在正常情况下不应违反密封性。每次你更改 private API of ClassA
(这......听起来很奇怪,但这就是我们在这里处理的),你将不得不同时更改 Accessor
和 AccessorImpl
来反映这一点。没有简单的解决方法,坦率地说,我认为这是一件好事。处理此类架构时应格外小心。