在開發權限管理系統時,常需將扁平的資料轉換為樹狀結構。原始碼中存在三個高度相似的方法(test、test2、test3),分別處理 Button→Model、Model→SubMenu、SubMenu→MainMenu 的層級關聯。本文展示如何利用 Apache Commons BeanUtils 的反射機制,將這三個方法重構為單一通用方法 testC,提升程式碼復用性與維護彈性。
原始測試類別 TestLogin 包含三個幾乎相同的私有方法:
public void test(Map<String,Model> modelmap, Button b) {
Model m = b.getModel();
if (!modelmap.containsKey(m.getId()))
modelmap.put(m.getId(), m);
modelmap.get(m.getId()).getButtons().add(b);
}
public void test2(Map<String,Menu> menumap, Model m) {
Menu menu = m.getMenu();
if (!menumap.containsKey(menu.getId()))
menumap.put(menu.getId(), menu);
menumap.get(menu.getId()).getModels().add(m);
}
public void test3(Map<String,Menu> menumap, Menu smenu) {
Menu fmenu = smenu.getMenu();
if (!menumap.containsKey(fmenu.getId()))
menumap.put(fmenu.getId(), fmenu);
menumap.get(fmenu.getId()).getMenus().add(smenu);
}
三者邏輯一致:取得父物件、以父物件 ID 為 Key 存入 Map、將子物件加入父物件的集合屬性。差異僅在於參數型別、父屬性名稱、子集合屬性名稱。
引入 BeanUtils.getDeclaredProperty 透過反射動態存取屬性,定義通用方法:
@SuppressWarnings("unchecked")
public void testC(Map map, Object t, String parent, String childrenName) {
try {
Object tParent = BeanUtils.getDeclaredProperty(t, parent);
Object tParentId = BeanUtils.getDeclaredProperty(tParent, "id");
if (!map.containsKey(tParentId)) {
map.put(tParentId, tParent);
}
Object parentObj = map.get(tParentId);
Collection childrenCollection = (Collection) BeanUtils.getDeclaredProperty(parentObj, childrenName);
childrenCollection.add(t);
} catch (IllegalAccessException | NoSuchFieldException e) {
e.printStackTrace();
}
}
BeanUtils.getDeclaredProperty 實作細節:
static public Object getDeclaredProperty(Object object, String propertyName)
throws IllegalAccessException, NoSuchFieldException {
Assert.notNull(object);
Assert.hasText(propertyName);
Field field = object.getClass().getDeclaredField(propertyName);
return getDeclaredProperty(object, field);
}
testall 方法大幅精簡,僅需傳入對應的屬性名稱:
public void testall(List<Button> buttonList) {
for (Button button : buttonList) {
testC(modelmap, button, "model", "buttons");
}
for (Model model : modelmap.values()) {
testC(smenumap, model, "menu", "models");
}
for (Menu menu : smenumap.values()) {
testC(fmenumap, menu, "menu", "menus");
}
// 列印樹狀結構驗證...
}
testC,無需新增方法此模式適用於「固定演算法、可變屬性名稱」的場景。使用時請注意:
id 屬性且可透過 Getter 存取parent)與子集合屬性(childrenName)名稱必須準確對應實際欄位new ArrayList()),否則會拋出 NullPointerExceptionprintStackTrace