Groovy: 有没有比copyWith方法更好的处理@Immutable对象的方法
Groovy: Is there a better way of handling @Immutable objects than copyWith method
我正在 groovy 中寻找一种 "modifying" 不可变对象的灵活方式(复制时更改了某些值)。有一个 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 集成测试代码实际上需要它,因此可读性和表现力很重要。上面的版本不能使用 "on the fly" 所以为了避免创建大量的辅助方法,我实现了自己的 copyOn (因为 copyWith 被采用)方法使得可以写:
def deliveryWithNewStreet = delivery.copyOn { it.recipient.address.street = newStreet }
不过,我想知道是否有最终的解决方案,存在于 groovy 中或由某些外部库提供。谢谢
为了完整起见,我提供了 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 中添加委托方法即可 class:
Delivery copyOn(Closure closure) {
CopyingDelegate.copyOn(this, closure)
}
高级解释:
首先需要注意的是代码:delivery.recipient.address.street = newStreet
被解释为:
- 正在访问
delivery
对象的 recipient
属性
- 正在访问
address
上面的结果
- 正在为 属性
street
分配 newStreet 的值
当然 class CopyingProxy
没有这些属性,所以 propertyMissing
方法将被涉及。
正如您所见,它是由 运行 setProperty
终止的 propertyMissing
方法调用链。
基本案例
为了实现所需的功能,我们维护两个字段:nextToCopy
(开头为delivery)和copyingClosure
(初始化作为使用 @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)
调用期间会发生什么。因此,我们将在柯里化函数中捕获当前 nextToCopy
(交付对象)、copyingClosure
(基于 copyWith 的简单复制)和 propertyName
(recipient) - partialCopy
.
然后这个复制将被合并到一个闭包中
{ object, property, value -> partialCopy(object.copyWith(createMap(property, value))) }
这将成为我们的新 copyingClosure
。在下一步中,将按照 Base Case 部分中描述的方式调用此 copyingClojure
。
结论
然后我们执行了: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'
}
我正在 groovy 中寻找一种 "modifying" 不可变对象的灵活方式(复制时更改了某些值)。有一个 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 集成测试代码实际上需要它,因此可读性和表现力很重要。上面的版本不能使用 "on the fly" 所以为了避免创建大量的辅助方法,我实现了自己的 copyOn (因为 copyWith 被采用)方法使得可以写:
def deliveryWithNewStreet = delivery.copyOn { it.recipient.address.street = newStreet }
不过,我想知道是否有最终的解决方案,存在于 groovy 中或由某些外部库提供。谢谢
为了完整起见,我提供了 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 中添加委托方法即可 class:
Delivery copyOn(Closure closure) {
CopyingDelegate.copyOn(this, closure)
}
高级解释:
首先需要注意的是代码:delivery.recipient.address.street = newStreet
被解释为:
- 正在访问
delivery
对象的recipient
属性 - 正在访问
address
上面的结果 - 正在为 属性
street
分配 newStreet 的值
当然 class CopyingProxy
没有这些属性,所以 propertyMissing
方法将被涉及。
正如您所见,它是由 运行 setProperty
终止的 propertyMissing
方法调用链。
基本案例
为了实现所需的功能,我们维护两个字段:nextToCopy
(开头为delivery)和copyingClosure
(初始化作为使用 @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)
调用期间会发生什么。因此,我们将在柯里化函数中捕获当前 nextToCopy
(交付对象)、copyingClosure
(基于 copyWith 的简单复制)和 propertyName
(recipient) - partialCopy
.
然后这个复制将被合并到一个闭包中
{ object, property, value -> partialCopy(object.copyWith(createMap(property, value))) }
这将成为我们的新 copyingClosure
。在下一步中,将按照 Base Case 部分中描述的方式调用此 copyingClojure
。
结论
然后我们执行了: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'
}