假设我们有一组表示某个系统的域设计的类:
@Immutable(copyWith = true) class Delivery { String id Person recipient List<Item> items } @Immutable(copyWith = true) class Person { String name Address address } @Immutable(copyWith = true) class Address { String street String postalCode }
我们假设我需要改变送货接收者的街道.在常规可变对象的情况下,执行:
delivery.recipient.address.street = newStreet
或者(在某些情况下可能有用):
delivery.with {recipient.address.street = newStreet}
当谈到对不可变对象做同样的事情时,根据我的知识,最好的方法是:
def recipient = delivery.recipient def address = recipient.address delivery.copyWith(recipient: recipient.copyWith(address: address.copyWith(street: newStreet)))
它实际上是Spock集成测试代码所需要的,因此可读性和表现力很重要.上面的版本不能“动态”使用,所以为了避免创建大量的辅助方法,我已经实现了我自己的copyOn(因为copyWith被采用)方法,这使得它可以编写:
def deliveryWithNewStreet = delivery.copyOn {it.recipient.address.street = newStreet}
我想知道是否有最终的解决方案,存在于groovy或由一些外部库提供.谢谢
解决方法
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,value -> partialCopy(object.copyWith(createMap(property,value))) } nextToCopy = nextToCopy.getProperties()[propertyName] return this } void setProperty(String property,Object value) { result = copyingClosure.call(nextToCopy,value) reset() } private void reset() { nextToCopy = result copyingClosure = simplyCopy } }
Delivery copyOn(Closure closure) { CopyingDelegate.copyOn(this,closure) }
高级解释:
首先,需要注意以下代码:delivery.recipient.address.street = newStreet被解释为:
>访问交付对象的收件人属性
>访问上述结果的地址
>使用newStreet的值分配属性street
当然,CopyingProxy类没有任何属性,因此将涉及propertyMissing方法.
因此,您可以看到它是一个propertyMissing方法调用链,通过运行setProperty终止.
基本情况
为了实现所需的功能,我们保留了两个字段:nextToCopy(在开头交付)和replicationClosure(使用@Immutable(copyWith = true)转换提供的copyWith方法初始化为简单副本).
此时,如果我们有一个像delivery.copyOn {it.id =’123′}这样的简单代码,那么根据simplyCopy和setProperty实现它将被评估为delivery.copyWith [id:’123′].
递归步骤
现在让我们看看如何使用更多级别的复制:delivery.copyOn {it.recipient.name =’newName’}.
首先,我们将在创建CopyingProxy对象时设置nextToCopy和copyingClosure的初始值,方法与上一个示例相同.
现在让我们分析在第一次propertyMissing(String propertyName)调用期间会发生什么.因此,我们将在curried函数 – partialCopy中捕获当前的nextToCopy(传递对象),replicationClosure(基于copyWith的简单复制)和propertyName(收件人).
然后这个复制将被合并到一个闭包中
{ object,value -> partialCopy(object.copyWith(createMap(property,value))) }
这成为我们新的复制关闭.在下一步中,将以Base Case部分中描述的方式调用replicationClojure.
结论
然后我们执行:delivery.recipient.copyWith [name:’newName’].然后将partialCopy应用于给我们delivery.copyWith [recipient:delivery.recipient.copyWith(name:’newName’)]的结果
最重要的是,您可以看到一些摆弄结果字段和重置功能.需要在一个闭包中支持多个作业:
delivery.copyOn { it.recipient.address.street = newStreet it.id = 'newId' }