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

时间:2016-03-11 11:10:46

标签: groovy immutability

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

1 个答案:

答案 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的代码被解释为:

  1. 访问recipient对象的delivery属性
  2. 访问address的结果
  3. 分配值为{em> newStreet
  4. 的属性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对象时设置copyingClosureCopyingProxy的初始值,方法与前面的示例相同。

现在让我们分析在第一次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