我是否可以在不了解其上下文的情况下更新深层嵌套的不可变对象?

时间:2017-08-05 18:40:39

标签: oop kotlin immutability

让我们假设我有一个嵌套的不可变对象图,沿着这些线(使用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)  

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

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

如何重写这个,以便每个对象都可以负责提供自己的变异逻辑,而不必知道完整的对象图?或者我只是想以一种不可能的方式与面向对象和功能概念结合?

2 个答案:

答案 0 :(得分:0)

我想到的一种可能的方法(顺便问一下这是提问者)是这样的:

data class Kettle(val on: Boolean) {
    fun activate() {
        return Transform(this, Kettle(on = true))
    }    
}

class Transform<T>(val what: T, val replacement: T) {
    fun <U> apply(x: U): U {
        if (x is T && x == what) {
            return replacement as U
        } else {
            return x
        }
    }
}

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

所以你这样使用它:

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. 提供了自己的愚蠢版本
  3. 在图表中的每个对象上调用一个函数只是为了更改一个函数,这似乎很奇怪(而且效率低下)。
  4. 但它确实实现了我的水壶目标,不需要了解它的上下文,并且编写activate函数非常简单。想法?

答案 1 :(得分:0)

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

val kettleLens = +House::kitchen + Kitchen::kettle

var house = House(...)
house = kettleLens(house) { copy(on = true) }