让我们假设我有一个嵌套的不可变对象图,沿着这些线(使用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)
(实际上,它稍微复杂一点,但这个伪代码会这样做。)
我不喜欢这样,不是因为它本身很长,而是因为水壶现在不仅要知道它自己的内部状态,还需要了解它所存在的完整背景。
如何重写这个,以便每个对象都可以负责提供自己的变异逻辑,而不必知道完整的对象图?或者我只是想以一种不可能的方式与面向对象和功能概念结合?
答案 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
一个机会来修改它想要的每个对象,它只会将它应用于一个。
这种方法很糟糕,因为:
transformEverything
方法但它确实实现了我的水壶目标,不需要了解它的上下文,并且编写activate
函数非常简单。想法?
答案 1 :(得分:0)
这是功能性镜头显示其力量的地方。例如,使用poetix/klenses,
val kettleLens = +House::kitchen + Kitchen::kettle
var house = House(...)
house = kettleLens(house) { copy(on = true) }