Groovy:有没有比copyWith方法更好的方法来处理@Immutable对象

Groovy:有没有比copyWith方法更好的方法来处理@Immutable对象,groovy,immutability,Groovy,Immutability,我正在寻找一种灵活的方式,在groovy中“修改”(复制一些更改的值)不可变对象。有一个copyWith方法,但它只允许您替换对象的某些属性。这似乎不够方便 假设我们有一组类代表某个系统的域设计: @Immutable(copyWith = true) class Delivery { String id Person recipient List<Item> items } @Immutable(copyWith = true) class Person

我正在寻找一种灵活的方式,在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
}
或者(在某些情况下可能有用):

就我所知,在对不可变对象执行相同操作时,最好的方法是:

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中,或者由一些外部库提供。谢谢

为了完整起见,我提供了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.recipient.address.street=newStreet
的代码被解释为:

  • 访问
    delivery
    对象的
    recipient
    属性
  • 访问上述结果的
    地址
  • 使用newStreet的值分配属性
    street
  • 当然,类
    CopyingProxy
    没有任何这些属性,因此将涉及
    propertyMissing
    方法

    如您所见,这是一个
    属性missing
    方法调用链,通过运行
    setProperty
    终止

    基本情况 为了实现所需的功能,我们维护两个字段:
    nextToCopy
    (在开始时交付)和
    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(stringpropertyname)
    调用期间会发生什么。因此,我们将在当前函数中捕获当前的
    nextToCopy
    (传递对象)、
    copyingClosure
    (基于copyWith的简单复制)和
    propertyName
    (收件人)

    然后,该副本将合并到一个闭包中

    { 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')]的结果

    因此,它基本上是一个带有方法调用的
    copy树

    除此之外,您还可以看到一些对
    结果
    字段和
    重置
    功能的摆弄。要求在一次关闭中支持多个任务:

    delivery.copyOn { 
        it.recipient.address.street = newStreet
        it.id = 'newId' 
    }
    

    您的copyOn实施有什么问题?
    Delivery copyOn(Closure closure) {
        CopyingDelegate.copyOn(this, closure)
    }
    
    { object, property, value -> partialCopy(object.copyWith(createMap(property, value))) }
    
    delivery.copyOn { 
        it.recipient.address.street = newStreet
        it.id = 'newId' 
    }