Search code examples
javamergemaintainability

How to write maintainable merge() method?


Let's say we have the Person entity:

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;
}

And I we have two Persons: original Person and updated Person:

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

I want to merge updated Person with original Person, so I wrote the following simple method:

// 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;
}

It works fine but I see some problems:
every time there will be added new field to the Person class, the programmer should not to forget to update the merge() method and in case the Person have really many fields then it will be difficult to maintain this method.

So my question is: is there any smarter/robust way to merge the state of the objects without using reflection feature of the language, so that you can be sure that all and only needed fields are merged? Thanks in advance!

UPD:

Originally I asked if there is the way of writing it without of using reflection but forget to say that it is not the restriction! I should also say that I had an idea to write this method with reflection + annotating "merge-able" fields with some custom annotation and then just skip fields without annotation. So, the intention of these words: "without using reflection" is to discover other maybe not so obvious solutions :)

Inspiration for this question was this functional style method: (which ensures that resource will be closed, take it only as example of not so obvious solution and safe programming)

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

Solution

  • I see 3 features you need:

    • "id" must not be modified
    • a change count should be returned
    • any change on "updated" should be applied to "original"

    Especially the first 2 requirements are a problem. I don't think that there's any library based solution which will do exactly this. However, you can write your own "merger" with some Java reflexion:

    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;
    }