我可以更新深层嵌套的不可变对象,而不必知道它的上下文吗?

让我们想象我有一个嵌套的不可变对象图,沿着这些线(使用Kotlin语法,但希望很清楚):

data class House(val bedroom: Bedroom, val bathroom: Bathroom, val kitchen: Kitchen) class Bedroom class Bathroom data class Kitchen(val oven: Oven, val kettle: Kettle) class Oven data class Kettle(val on: Boolean) var house = House(Bedroom(), Bathroom(), Kitchen(Oven(), Kettle(false))) 

现在,我想打开水壶。 如果对象是可变的,我只会写:

 data class Kettle(var on: Boolean) { fun activate() { this.on = true } } house.kitchen.kettle.activate() 

但是因为它们是不变的,所以我必须写下:

 data class Kettle(val on: Boolean) { fun activate(house: House): House { return house.copy(kitchen = kitchen.copy(kettle = kettle.copy(on = true))) } } house = house.kitchen.kettle.activate(house) 

(实际上,它稍微复杂一些,但是这个伪代码将会这样做)。

我不喜欢这样,并不是因为它本身就很长,而是因为水壶现在不仅需要了解自己的内部状态,还需要了解它存在的全部背景。

我怎么能重写这个,使每个对象可以负责提供自己的变异逻辑,而不必知道完整的对象图? 或者我只是试图以不可能的方式来结合面向对象和function的概念?

我想到了一个可能的方法(顺便提一句,这个提问者)是这样的:

 data class Kettle(val on: Boolean) { fun activate() { return Transform(this, Kettle(on = true)) } } class Transform(val what: T, val replacement: T) { fun  apply(x: U): U { if (x is T && x == what) { return replacement as U } else { return x } } } 

这里的想法是,变换是一个函数,你可以应用到图中的每个对象,它只会修改你告诉它修改的内容。

所以你这样使用它:

 val transform = house.kitchen.kettle.activate() house = house.transformEverything(transform) 

每个类都有这样的实现:

 data class House(val bedroom: Bedroom, val bathroom: Bathroom, val kitchen: Kitchen) { fun transformEverything(transform: Transform): House { return transform(this).copy( bedroom = bedroom.transformEverything(transform), bathroom = bathroom.transformEverything(transform), kitchen = kitchen.transformEverything(transform) ) } } 

递归地给transform一个机会来修改它想要的每个对象,并且它将只适用于它。

这种做法是不好的,因为:

  1. 吨的锅炉板给一切它自己的哑transformEverything方法
  2. 在图表中的每个对象上调用一个函数只是为了改变一个函数,这似乎很奇怪(也很低效)。

但是它确实达到了我的目标,而不需要知道任何关于它的上下文的事情,而且写这个activate函数非常简单。 思考?

这是function镜头显示其力量的地方。 例如,使用poetix / klenses ,

 val kettleLens = +House::kitchen + Kitchen::kettle var house = House(...) house = kettleLens(house) { copy(on = true) }