如何编写可维护的merge()方法?

时间:2017-06-21 14:09:58

标签: java merge maintainability

假设我们有Person实体:

class Person {
    /*
        once the id is assigned, then must not be modified! 
        assume that the id will be assigned by the ORM framework
    */
    int id;
    String givenName;
    String familyName;
}

我们有两个人:原始人和更新的人:

Person original = new Person("Frantisek", "Makovicka");
Person updated = new Person("Viktor", "Makovicka");

我想将更新的Person与原始Person合并,因此我编写了以下简单方法:

// return number of changed fields
public int merge(Person original, Person updated) {
    int changes = 0;

    String oldGivenName = original.givenName;
    original.givenName = updated.givenName;
    if (changed(oldGivenName, original.givenName)) changes++;

    String oldFamilyName = original.familyName;
    original.familyName = updated.familyName;
    if (changed(oldFamilyName, original.familyName)) changes++;

    return changes;
}

它工作正常,但我看到一些问题:
每次向Person类添加新字段时,程序员都不应该忘记更新merge()方法,如果Person有很多字段,那么维护这个方法就很困难了。

所以我的问题是:有没有更智能/更健壮的方法来合并对象的状态而不使用该语言的反射功能,这样您就可以确保所有且仅需要的字段被合并了? 提前谢谢!

UPD

最初我问是否有写作的方式而不使用反射,但忘记说这不是限制!我还应该说我有一个想法,用反射+注释“merge-able”字段和一些自定义注释来编写这个方法,然后只跳过没有注释的字段。 因此,这些词语的意图:“不使用反思”是发现其他可能不那么明显的解决方案:)

这个问题的灵感来自于这种功能样式方法:(确保资源被关闭,仅作为不那么明显的解决方案和安全编程的例子)

public static void doWithResource(String name, Consumer<Resource> consumer) {
    Resource res = new Resource(name);
    consumer.accept(res);
    res.close();
}

3 个答案:

答案 0 :(得分:2)

我看到了你需要的3个功能:

  • “id”不得修改
  • 应返回更改计数
  • 对“已更新”的任何更改都应适用于“原始”

特别是前2个要求是个问题。我认为没有任何基于库的解决方案可以做到这一点。但是,您可以使用Java反射编写自己的“合并”:

private int merge(Person p1, Person p2) throws IllegalAccessException {
    int changes = 0;
    for(Field field: Person.class.getDeclaredFields()) {
        if(!field.getName().equals("id")) {
            field.setAccessible(true);
            Object originalField = field.get(p1);
            Object updatedField = field.get(p2);
            if(!originalField.equals(updatedField)) {
                field.set(p1, updatedField);
                changes++;
            }
        }
    }
    return changes;
}

答案 1 :(得分:1)

您可以使用java.beans.Introspector获取PropertyDescriptors。这接近于使用反射,事实上java.beans包在内部使用反射,但它至少有点干净,并且(大部分)仅限于bean方法(get / set / is methods):

public int merge(Person original, Person updated) {
    int changes = 0;

    try {
        BeanInfo info = Introspector.getBeanInfo(Person.class, Object.class);
        for (PropertyDescriptor property : info.getPropertyDescriptors()) {
            if (property.getName().equalsIgnoreCase("id")) {
                continue;
            }

            Method get = property.getReadMethod();
            Method set = property.getWriteMethod();
            if (set == null) {
                // Ignore read-only property.
                continue;
            }

            Object oldValue = get.invoke(original);
            Object newValue = get.invoke(updated);

            set.invoke(original, newValue);

            if (changed(oldValue, newValue)) {
                changes++;
            }
        }
    } catch (IntrospectionException | ReflectiveOperationException e) {
        // We should never get here.
        throw new RuntimeException(
            "Could not update properties of " + Person.class + ": " + e, e);
    }

    return changes;
}

然而,这总是执行浅拷贝。如果任何属性具有可变类型(如数组或集合类型,或理论上的可变对象类型,地址),并且如果您的方法不对这些类型进行防御性复制,则这两个对象将共享对象,导致沮丧,休眠的错误。

如果您的编码人员都知道要进行防御性复制,或者如果您确定没有人会添加具有可变类型的属性,那么这不是问题。否则,它将变得足够复杂,以至于可能不值得尝试自动复制属性;至少,您需要检查数组或集合类型并克隆该值。

答案 2 :(得分:0)

https://commons.apache.org/proper/commons-beanutils/可能对copyProperties(Object ToCopyTo, Obj ToCopyFrom)

有帮助