如何确保一个类只能用另一个类的对象实例化而不暴露内部结构?

时间:2019-03-07 07:19:35

标签: kotlin

假设我们有两个类,FooBarBar应该只能从Foo对象构造。

这是我目前的尝试,原因是我不认为它们是适当的解决方案:


尝试1:

class Foo {
    var data: Int = 0 // meh: This implementation detail should not be exposed to the public.
    fun add(x: Int) {
        data += x
    }
}

class Bar(foo: Foo) {
    private var data: Int = 2 * foo.data
    fun show() = println(data + 10)
}

fun main() {
    val myFoo = Foo()
    myFoo.add(3)
    myFoo.add(4)
    val myBar = Bar(myFoo)
    myBar.show()
}

问题:Foo::data从外面不可见。


尝试2:

class Foo {
    private var data: Int = 0
    fun add(x: Int) {
        data += x
    }

    fun makeBar() = Bar(2 * data)
}

class Bar(private val data: Int) { // meh: Bar should only be instantiated from a Foo.
    fun show() = println(data + 10)
}

fun main() {
    val myFoo = Foo()
    myFoo.add(3)
    myFoo.add(4)
    val myBar = myFoo.makeBar()
    myBar.show()
}

问题:每个人现在都可以创建Bar,即使手头没有Foo


来自C ++的第一个解决方案是将Bar的构造函数设为私有,但通过声明其为friend类仅让Foo对其进行访问。在Kotlin中是否可能有类似的事情,或者还有另一种(更好的)解决方案?

1 个答案:

答案 0 :(得分:4)

阅读用例之后,您可能正在寻找inner class。这是从您尝试使用内部类Bar得出的解决方案:

class Foo {
    private var data = 0 // private as you do not want to make it publicly available
    fun add(x: Int) {
        data += x
    }

    inner class Bar {
        private var data = 2 * this@Foo.data // access the outer foo directly... no need to hold it in the constructor
        fun show() = println(data + 10)
    }
}    

fun main() {
    val myFoo = Foo()
    myFoo.add(3)
    myFoo.add(4)
    // val myBar = Foo.Bar() // this does not compile... you need a foo to build a bar...
    val myBar = myFoo.Bar() // this works...
    myBar.show()
    // myBar.data // this wouldn't compile as it's not accessible
}

正如您所说,您更喜欢顶级课程。根据您希望Foo.data的安全程度,您可能还会对以下设置感兴趣:

class Foo {
    private var data = 0
    fun add(x: Int) {
        data += x
    }
    fun Bar.getFooData() = data
}

class Bar(foo: Foo) {
    private var data = 2 * with(foo) { getFooData() }
    fun show() = println(data + 10)
}

因此,只有在BarFoo都可用时(后者作为接收者),您才可以访问Foo的数据。但是,这也意味着,如果两个对象均可用,则您可以从外部访问Foo.data。以下在main中也将成功:

with (myFoo) {
  myBar.getFooData()
}

所以:让data不能从外部访问(忽略反射),嵌套类或内部类是您的朋友。使其难以访问(或:在使用前强制某些条件成立),带有适当接收者的扩展功能方法可能是您的朋友。