我正在寻找一种灵活的方式来“修改”(复制一些值已更改)groovy中的不可变对象。有一个copyWith方法,但它只允许您替换对象的某些属性。它看起来不够方便。
假设我们有一组表示某个系统的域设计的类:
@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或由一些外部库提供。感谢
答案 0 :(得分:0)
为了完整起见,我提供了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
的代码被解释为:
recipient
对象的delivery
属性address
的结果street
当然,类CopyingProxy
没有任何这些属性,因此将涉及propertyMissing
方法。
因此,如您所见,它是由运行propertyMissing
终止的setProperty
方法调用链。
为了实现所需的功能,我们维护两个字段:nextToCopy
(开始时是交付)和copyingClosure
(使用以下方式初始化为简单副本) copyWith
转换提供的@Immutable(copyWith = true)
方法。
在这一点上,如果我们有一个像delivery.copyOn { it.id = '123' }
这样的简单代码,那么根据delivery.copyWith [id:'123']
和simplyCopy
的实现,它将被评估为setProperty
。
现在让我们看看如何进行另一级复制:delivery.copyOn { it.recipient.name = 'newName' }
。
首先,我们将在创建nextToCopy
对象时设置copyingClosure
和CopyingProxy
的初始值,方法与前面的示例相同。
现在让我们分析在第一次propertyMissing(String propertyName)
通话期间会发生什么。因此,我们将在咖喱函数-{{中捕获当前的nextToCopy
(交付对象),copyingClosure
(基于copyWith的简单复制)和propertyName
(收件人) 1}}。
然后此副本将被包含在一个闭包中
partialCopy
这将成为我们的新{ object, property, value -> partialCopy(object.copyWith(createMap(property, value))) }
。在下一步中,以基本案例部分中所述的方式调用此copyingClosure
。
然后我们执行了:copyingClojure
。然后delivery.recipient.copyWith [name:'newName']
应用于给我们partialCopy
因此,它基本上是delivery.copyWith[recipient:delivery.recipient.copyWith(name:'newName')]
方法调用的树。
最重要的是,您会发现有些copyWith
字段和result
函数的摆弄。要求一次关闭支持多个任务:
reset