如何编写可维护的 merge() 方法?
How to write maintainable merge() method?
假设我们有 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添加新的字段class,程序员不要忘记更新merge()方法,如果Person有很多字段,那么维护这个方法会很困难。
所以我的问题是:有没有什么 smarter/robust 方法可以在不使用语言的反射功能的情况下合并对象的状态,这样您就可以确保合并所有且只需要的字段?
提前致谢!
UPD:
本来问有没有不使用反射的写法,忘了说不是限制!我还应该说,我有一个想法,就是用反射 + 用一些自定义注释注释 "merge-able" 字段来编写这个方法,然后跳过没有注释的字段。
所以,这些词的意图:"without using reflection" 是为了发现其他可能不是那么明显的解决方案:)
这个问题的灵感来自于这个函数式方法:(它确保资源将被关闭,仅作为不太明显的解决方案和安全编程的示例)
public static void doWithResource(String name, Consumer<Resource> consumer) {
Resource res = new Resource(name);
consumer.accept(res);
res.close();
}
https://commons.apache.org/proper/commons-beanutils/ 的 copyProperties(Object ToCopyTo, Obj ToCopyFrom)
可能对您有所帮助
我看到您需要 3 个功能:
- "id"不得修改
- 应返回更改计数
- 对 "updated" 的任何更改都应应用于 "original"
尤其是前2个要求有问题。我不认为有任何基于库的解决方案可以做到这一点。但是,您可以编写自己的 "merger" 并带有一些 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;
}
您可以使用 java.beans.Introspector to obtain PropertyDescriptors。这接近于使用反射,事实上 java.beans 包在内部使用反射,但它至少更干净一点,并且将(大部分)限制为 bean 方法(get/set/is 方法):
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;
}
但是,这总是执行浅拷贝。如果任何 属性 具有可变类型(如数组或集合类型,或可变对象类型,如理论上的地址),并且如果您的方法不对此类类型进行防御性复制,则这两个对象将是共享对象会导致令人沮丧的休眠错误。
如果您的编码人员都知道执行防御性复制,或者如果您确定没有人会添加具有可变类型的属性,那么这不是问题。否则,它会变得非常复杂,以至于不值得尝试自动复制属性;至少,您需要检查数组或集合类型并克隆值。
假设我们有 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添加新的字段class,程序员不要忘记更新merge()方法,如果Person有很多字段,那么维护这个方法会很困难。
所以我的问题是:有没有什么 smarter/robust 方法可以在不使用语言的反射功能的情况下合并对象的状态,这样您就可以确保合并所有且只需要的字段? 提前致谢!
UPD:
本来问有没有不使用反射的写法,忘了说不是限制!我还应该说,我有一个想法,就是用反射 + 用一些自定义注释注释 "merge-able" 字段来编写这个方法,然后跳过没有注释的字段。 所以,这些词的意图:"without using reflection" 是为了发现其他可能不是那么明显的解决方案:)
这个问题的灵感来自于这个函数式方法:(它确保资源将被关闭,仅作为不太明显的解决方案和安全编程的示例)
public static void doWithResource(String name, Consumer<Resource> consumer) {
Resource res = new Resource(name);
consumer.accept(res);
res.close();
}
https://commons.apache.org/proper/commons-beanutils/ 的 copyProperties(Object ToCopyTo, Obj ToCopyFrom)
我看到您需要 3 个功能:
- "id"不得修改
- 应返回更改计数
- 对 "updated" 的任何更改都应应用于 "original"
尤其是前2个要求有问题。我不认为有任何基于库的解决方案可以做到这一点。但是,您可以编写自己的 "merger" 并带有一些 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;
}
您可以使用 java.beans.Introspector to obtain PropertyDescriptors。这接近于使用反射,事实上 java.beans 包在内部使用反射,但它至少更干净一点,并且将(大部分)限制为 bean 方法(get/set/is 方法):
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;
}
但是,这总是执行浅拷贝。如果任何 属性 具有可变类型(如数组或集合类型,或可变对象类型,如理论上的地址),并且如果您的方法不对此类类型进行防御性复制,则这两个对象将是共享对象会导致令人沮丧的休眠错误。
如果您的编码人员都知道执行防御性复制,或者如果您确定没有人会添加具有可变类型的属性,那么这不是问题。否则,它会变得非常复杂,以至于不值得尝试自动复制属性;至少,您需要检查数组或集合类型并克隆值。