2017-06-21 6 views
0

Lassen Sie uns sagen, dass wir die Person Einheit haben:Wie schreibt man die wartbare merge() Methode?

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

Und ich, wir haben zwei Personen: original Person und aktualisiert Person:

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

Ich möchte mit Original-Person Person fusionieren aktualisiert, so schrieb ich die folgende einfache Methode:

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

Es funktioniert gut, aber ich sehe einige Probleme:
jedes Mal, Es wird ein neues Feld zur Person-Klasse hinzugefügt, der Programmierer sollte nicht vergessen, die merge() -Methode zu aktualisieren und falls die Person wirklich viele Felder hat, wird es schwierig sein, diese Methode beizubehalten.

Also meine Frage ist: Gibt es eine kluge/robuste Möglichkeit, den Zustand der Objekte ohne Verwendung der Reflexionsfunktion der Sprache zu verschmelzen, so dass Sie sicher sein können, dass alle und nur benötigte Felder zusammengeführt werden? Vielen Dank im Voraus!

UPD:

Ursprünglich fragte ich, ob es die Art und Weise des Schreibens ohne Reflexion der Verwendung aber vergessen zu sagen, dass es nicht die Einschränkung ist! Ich sollte auch sagen, dass ich eine Idee hatte, diese Methode mit Reflection + Annotations "Merge-fähigen" Felder mit einigen benutzerdefinierten Annotation zu schreiben und dann einfach Felder ohne Annotation überspringen. die Absicht dieser Worte also: „unreflektiert mit“ ist, andere vielleicht entdecken nicht so offensichtlich Lösungen :)

Inspiration für diese Frage wurde diese funktionale Stil-Methode: (welche die Ressource geschlossen wird gewährleistet, nehmen Sie es nur als Beispiel für nicht so offensichtliche Lösung und sichere Programmierung)

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

geändert ist nicht definiert. Sie müssen Änderungen bedeuten ++. Wie es jetzt eingerichtet wird, erhalten Sie immer 0 von der obigen Methode zurück. – Inxsible

+0

Sie überschreiben das Original mit dem aktualisierten, in diesem Fall sollten Sie wahrscheinlich nur den Originalzeiger überschreiben und keine Merge-Methode aufrufen. Wenn Sie dies tun müssen, denke ich, dass Sie nach Reflexion suchen. (Der Hauptunterschied ist, wenn es sich um zwei verschiedene Klassen handelt, die die Person erweitern, sollte die Verschmelzung sinnvoll sein?) – Tezra

+0

Sie könnten etwas nachdenken und jedes (vielleicht annotierte) Feld Ihrer Klasse betrachten. – danielspaniol

Antwort

2

ich sehe drei Funktionen, die Sie brauchen:

  • „id“ darf nicht
  • ein Änderungszählwert geändert werden sollte
  • jede Änderung auf „aktualisiert“ sollte „original“

Vor allem die ersten zwei Anforderungen sind ein Problem angewendet werden zurückgeschickt werden . Ich glaube nicht, dass es eine bibliotheksbasierte Lösung gibt, die genau das tut. Allerdings können Sie Ihre eigene „Fusion“ mit einigen Java Reflexion schreiben:

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

Frage sagt "ohne Reflektion zu benutzen" - ich weiß nicht warum. – slim

+0

@Thomas Uhrig Vielen Dank für Ihre Antwort! Ich frage mich, ob die Gleichheitsoperation, die für Objekte des rohen Objekttyps ausgeführt wird, richtig mit der Gleichung von Feldern von Typen umgehen wird, die die Methoden equals(), hashCode() überschrieben haben? Wird es nicht nur den Typvergleich durchführen, wie er in der Objektklasse definiert ist? Public boolean equals (Objekt obj) {return (this == obj); } '? – uanacau

1

Sie java.beans.IntrospectorPropertyDescriptors erhalten verwenden können. Dies ist nah an der Verwendung von Reflektion, und tatsächlich verwendet das java.beans-Paket interne Reflektion, aber es ist zumindest ein wenig sauberer und wird (hauptsächlich) auf Bean-Methoden beschränkt (get/set/is-Methoden):

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

Dies führt jedoch immer eine flache Kopie.Wenn eine Eigenschaft einen veränderlichen Typ hat (wie ein Array oder ein Collection-Typ oder ein veränderbarer Objekttyp, wie zum Beispiel Address), und wenn Ihre Methoden kein defensives Kopieren solcher Typen durchführen, werden die beiden Objekte Objekte teilen, die dies tun führen zu frustrierenden, schlafenden Bugs.

Wenn Ihre Programmierer das defensive Kopieren ausführen, oder wenn Sie sicher sind, dass niemand Eigenschaften mit veränderbaren Typen hinzufügen wird, ist das kein Problem. Sonst wird es kompliziert genug, dass es sich nicht lohnt, das Kopieren von Eigenschaften zu automatisieren; Zumindest müssen Sie nach einem Array oder Sammlungstyp suchen und den Wert klonen.

+0

Vielen Dank für Ihre interessante Antwort! Ich schaue darauf und versuche den Vorteil dieses Ansatzes gegenüber klarer Reflexion zu erfassen. Kannst du das bitte klären? > und wird (meistens) auf Bean-Methoden beschränkt sein (get/set/is methods) Was bedeutet das? Mehr über Ich denke, dass Ansatz mit klarer Reflexion ist flexibler, weil ich z. B. annotierte Felder oder veränderbare Felder (dh Sammlungen wie gesagt) – uanacau

+0

überspringen kann Eine Bean-Eigenschaft wird durch eine Getter-Methode, deren Name, nach dem Java dargestellt Die Beans-Spezifikation muss mit "get" beginnen, es sei denn, ihr Rückgabetyp ist "boolean". In diesem Fall muss sie entweder mit "get" oder "is" beginnen. Wenn die Bean-Eigenschaft nicht schreibgeschützt ist, verfügt sie auch über eine Setter-Methode, deren Name mit 'set' beginnen muss. Das Paket java.beans hält sich an diese Regeln. – VGR

Verwandte Themen