请看以下玩具示例:
case class Person(name: String, address: Person#Address = null) {
case class Address(street: String, city: String, state: String) {
def prettyFormat = s"To $name of $city" // note I use name here
}
def setAddress(street: String, city: String, state: String): Person =
copy(address=Address(street,city,state))
def setName(n: String): Person = copy(name=n)
}
你知道那里有虫子吗?是的,以下代码将在两种情况下打印相同的消息(John):
val p1 = Person("John").setAddress("Main", "Johntown", "NY")
println(p1.address.prettyFormat) // prints To John of Johntown
val p2 = p1.setName("Jane")
println(p2.address.prettyFormat) // prints To John of Johntown
当然这是因为地址中的$ outer引用在set方法中保留,所以p2内部对象仍然引用John。可以通过以下方法或通过重新创建Address对象来修复该问题(如果我们在case类中预先创建了复制构造函数,那会不会很好?):
def setName(n: String) = copy(name=n).setAddress(address.street,address.city,address.state)
但是,如果有像这样的内部对象和像setName这样的数十种方法,问题会变得更加烦人。所以我的结论是不变性和类内部类是互不相容的。
问题:嵌套不可变对象的结构是否存在设计模式或有用的习惯用法,其中内部对象需要访问外部对象才能完成工作。
到目前为止,我已经考虑将person作为隐式传递给prettyFormat或将内部方法包装到Reader monad中,因此当前人将应用于prettyFormat返回的monad。还有其他很棒的想法吗?
答案 0 :(得分:6)
不变性和类内部类不是互不兼容的,但是当你创建内部地址类时,它会绑定到那个Person实例(否则你会使用一个静态内部类,即在一个伴随中定义地址)宾语)。
问题更多的是为案例类提供的copy
方法的语义,它不考虑内部类。因此要么你放弃不变性,要么在修改时创建一个真正的新人:
def setName(n: String): Person = Person(n, street, city, state)
请注意,我不应该将直接Address
实例传递给Person()
,您的定义是每个地址类型都是一个人的一部分,只对那个人有意义,所以它可以不存在于那个人之外,我无法将它从外部传递给正在创造的新人。同样,如果不是这种情况,那么你需要用不同的语义重新思考你的结构。
就个人而言,我认为以下内容更为清晰/直观:
case class Address(street: String, city: String, state: String)
case class Person(name: String, address: Address) {
def prettyFormat = s"To $name of ${address.city}"
}
然后,您可以创建地址/人员的副本,而不用担心和完全不变。
答案 1 :(得分:0)
问题:嵌套不可变对象的结构是否存在设计模式或有用的习惯用法,其中内部对象需要访问外部对象才能完成工作。
我不这么认为,不。
“外部”对象必然会引用“内部”对象。
通过使用嵌套类,您已经从“内部”对象创建了对“外部”对象的(隐藏)引用。如您在示例中所见,这会混淆Scala中的“案例类”机制。
因此,你的两个班级是1-1,并且在设计上相互依存。如果它们真的是1-1,那么将它们放在一个类中会更简单和清晰。