【问题标题】:Groovy: Is there a better way of handling @Immutable objects than copyWith methodGroovy:有没有比 copyWith 方法更好的处理@Immutable 对象的方法
【发布时间】:2016-03-11 11:10:45
【问题描述】:

我正在寻找一种灵活的方式在 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 实施有什么问题?

标签: groovy immutability


【解决方案1】:

为了完整起见,我提供了 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. 访问delivery对象的recipient属性
  2. 访问address 了解上述结果
  3. newStreet 的值分配属性 street

当然CopyingProxy类没有这些属性,所以会涉及到propertyMissing方法。

如您所见,它是由运行setProperty 终止的propertyMissing 方法调用链。

基本情况

为了实现所需的功能,我们维护了两个字段:nextToCopy(开头是delivery)和copyingClosure(使用copyWith 方法初始化为简单副本由@Immutable(copyWith = true)转换提供)。

此时,如果我们有一个像delivery.copyOn { it.id = '123' } 这样的简单代码,那么根据simplyCopysetProperty 实现,它将被评估为delivery.copyWith [id:'123']

递归步骤

现在让我们看看它如何与多一层复制一起工作:delivery.copyOn { it.recipient.name = 'newName' }

首先,我们将在创建CopyingProxy 对象时设置nextToCopycopyingClosure 的初始值,方法与前面的示例相同。

现在让我们分析在第一次propertyMissing(String propertyName) 调用期间会发生什么。因此,我们将在柯里化函数 partialCopy 中捕获当前的 nextToCopy(交付对象)、copyingClosure(基于 copyWith 的简单复制)和 propertyNamerecipient)。

然后这个复制将被合并到一个闭包中

{ object, property, value -> partialCopy(object.copyWith(createMap(property, value))) }

这成为我们新的copyingClosure。在下一步中,这个copyingClojure 将按照Base Case 部分中描述的方式调用。

结论

然后我们执行了: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' 
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-12
    相关资源
    最近更新 更多