为了完整起见,我提供了 copyOn 方法的实现。如下:
class CopyingDelegate {
static <T> T copyOn(T source, Closure closure) {
def copyingProxy = new CopyingProxy(source)
closure.call(copyingProxy)
return (T) copyingProxy.result
}
}
class CopyingProxy {
private Object nextToCopy
private Object result
private Closure copyingClosure
private final Closure simplyCopy = { instance, property, value -> instance.copyWith(createMap(property, value)) }
private final def createMap = { property, value -> def map = [:]; map.put(property, value); map }
CopyingProxy(Object nextToCopy) {
this.nextToCopy = nextToCopy
copyingClosure = simplyCopy
}
def propertyMissing(String propertyName) {
def partialCopy = copyingClosure.curry(nextToCopy, propertyName)
copyingClosure = { object, property, value ->
partialCopy(object.copyWith(createMap(property, value)))
}
nextToCopy = nextToCopy.getProperties()[propertyName]
return this
}
void setProperty(String property, Object value) {
result = copyingClosure.call(nextToCopy, property, value)
reset()
}
private void reset() {
nextToCopy = result
copyingClosure = simplyCopy
}
}
然后只需在 Delivery 类中添加委托方法即可:
Delivery copyOn(Closure closure) {
CopyingDelegate.copyOn(this, closure)
}
高级解释:
首先需要注意的是:delivery.recipient.address.street = newStreet的代码被解释为:
- 访问
delivery对象的recipient属性
- 访问
address 了解上述结果
- 用 newStreet 的值分配属性
street
当然CopyingProxy类没有这些属性,所以会涉及到propertyMissing方法。
如您所见,它是由运行setProperty 终止的propertyMissing 方法调用链。
基本情况
为了实现所需的功能,我们维护了两个字段:nextToCopy(开头是delivery)和copyingClosure(使用copyWith 方法初始化为简单副本由@Immutable(copyWith = true)转换提供)。
此时,如果我们有一个像delivery.copyOn { it.id = '123' } 这样的简单代码,那么根据simplyCopy 和setProperty 实现,它将被评估为delivery.copyWith [id:'123']。
递归步骤
现在让我们看看它如何与多一层复制一起工作:delivery.copyOn { it.recipient.name = 'newName' }。
首先,我们将在创建CopyingProxy 对象时设置nextToCopy 和copyingClosure 的初始值,方法与前面的示例相同。
现在让我们分析在第一次propertyMissing(String propertyName) 调用期间会发生什么。因此,我们将在柯里化函数 partialCopy 中捕获当前的 nextToCopy(交付对象)、copyingClosure(基于 copyWith 的简单复制)和 propertyName(recipient)。
然后这个复制将被合并到一个闭包中
{ object, property, value -> partialCopy(object.copyWith(createMap(property, value))) }
这成为我们新的copyingClosure。在下一步中,这个copyingClojure 将按照Base Case 部分中描述的方式调用。
结论
然后我们执行了:delivery.recipient.copyWith [name:'newName']。然后将partialCopy 应用到给我们delivery.copyWith[recipient:delivery.recipient.copyWith(name:'newName')] 的结果上
所以它基本上是copyWith 方法调用的树。
除此之外,您还可以看到对 result 字段和 reset 函数的一些摆弄。它需要在一个闭包中支持多个任务:
delivery.copyOn {
it.recipient.address.street = newStreet
it.id = 'newId'
}