2017-06-21 71 views
0

比方說,我們有專人實體:如何編寫可維護的merge()方法?

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"); 

我想合併更新的人用原來的人,所以我寫了以下簡單的方法:

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

本來我問是否有寫它,而無需使用反射,但忘了說,這是不限制的方式!我還應該說,我有一個想法,用反射+註釋「可合併」字段和一些自定義註釋來編寫此方法,然後跳過沒有註釋的字段。 因此,這些詞的意圖:「不使用反射」是發現其他可能不是那麼明顯的解決方案:)

對這個問題的啓示是這種功能樣式的方法:(確保資源將被關閉,僅作爲例子不是那麼顯而易見的解決方案和安全編程)的

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

更改未定義。你必須改變++。現在設置它的方式,你將總是從上面的方法返回0。 – Inxsible

+0

您正在使用更新來覆蓋原始文件,在這種情況下,您可能應該只是覆蓋原始指針,而不是調用合併方法。如果你必須這樣做,我認爲你正在尋找反思。 (主要區別在於,如果這些是擴展Person的兩個不同類,那麼合併應注意嗎?) – Tezra

+0

您可以做一些反思並查看您班級的每個(可能註釋過的)字段。 – danielspaniol

回答

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

問題說「沒有使用反射」 - 我不知道爲什麼。 – slim

+0

@Thomas Uhrig謝謝你的回答!我想知道在原始對象類型的對象上執行的相等操作是否能正確處理覆蓋equals(),hashCode()方法的類型的字段方程?它不會僅僅執行類型比較,因爲它在Object類中定義了'public boolean equals(Object obj){return(this == obj); }'? – uanacau

1

您可以使用java.beans.Introspector獲得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; 
} 

但是,這總是執行淺拷貝。如果任何屬性具有可變類型(如數組或集合類型,或理論上爲Address的可變對象類型),並且如果您的方法不對這些類型進行防禦性複製,則這兩個對象將共享對象導致沮喪,休眠的錯誤。

如果您的編碼人員都知道執行防禦性複製,或者如果您確定沒有人會添加具有可變類型的屬性,則不是問題。否則,它會變得非常複雜,因此可能不值得嘗試自動複製屬性;至少,您需要檢查數組或集合類型並克隆該值。

+0

謝謝你的有趣答案!我正在研究並試圖通過清晰的思考來把握這種方法的優勢。你能澄清一下嗎? >並將(主要)限制爲bean方法(get/set/is方法) 它意味着什麼? 更多我認爲具有清晰反射的方法更加靈活,因爲我可以用於跳過註釋字段或可變字段(即,如您所說的集合) – uanacau

+0

Bean屬性由getter方法表示,其名稱根據Java Beans規範必須以'get'開始,除非它的返回類型是'boolean',在這種情況下它必須以'get'或'is'開頭。如果bean屬性不是隻讀的,它也有一個setter方法,其名稱必須以'set'開頭。 java.beans包遵守這些規則。 – VGR