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

我正在寻找一种灵活的方式来“修改”(复制一些值已更改)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或由一些外部库提供.谢谢

解决方法

为了完整起见,我提供了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,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类中添加委托方法的问题:

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

所以它基本上是一个copyWith方法调用树.

最重要的是,您可以看到一些摆弄结果字段和重置功能.需要在一个闭包中支持多个作业:

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

相关文章

背景:    8月29日,凌晨4点左右,某服务告警,其中一个...
https://support.smartbear.comeadyapi/docs/soapui/steps/g...
有几个选项可用于执行自定义JMeter脚本并扩展基线JMeter功能...
Scala和Java为静态语言,Groovy为动态语言Scala:函数式编程,...
出处:https://www.jianshu.com/p/ce6f8a1f66f4一、一些内部...
在运行groovy的junit方法时,报了这个错误:java.lang.Excep...