"當系統的每一部分都由最優解或相對優解組成,那麼系統最終也將是最完美的。" 這句話是在參加莫技術分享會上聽到的,這句話吸引我占在人群後面聽完了她的分享,確實受益良多。 本文也旨在描述自己在項目演變中對一處公共處理邏輯優化的過程,周期略長最近有時間整理如下。 業務系統數據傳遞過程中,會抽取一些公共的屬 ...
"當系統的每一部分都由最優解或相對優解組成,那麼系統最終也將是最完美的。"
這句話是在參加莫技術分享會上聽到的,這句話吸引我占在人群後面聽完了她的分享,確實受益良多。
本文也旨在描述自己在項目演變中對一處公共處理邏輯優化的過程,周期略長最近有時間整理如下。
業務系統數據傳遞過程中,會抽取一些公共的屬性和方法封裝為特定基類以便於後續開發進行繼承。
這些被抽象出來的擁有公共屬性的基類,在業務流轉過程中的賦值也應當進行統一妥善的處理。
看到這裡的小伙伴們可能心中有點疑惑,在業務組織 Class_A/Class_B/...將基類中基本屬性順勢填充不就可以了嗎?
實際業務中 BaseClass 中的屬性需要根據各種不同的場景進行演變和推算,你確定將這些公共推算方式放入業務邏輯中合適嗎??
隨著系統擴展繼承 BaseClass 的子類會膨脹,所有的子類都需要進行父類基本屬性賦值,代碼看起來是不是有點重覆?
當公共屬性推算方式發生變化,修改所有子類計算方式和修改公共方法,工作量孰重孰輕?
抽取成公共方法這點毋庸置疑的,但怎樣高效和優雅的書寫代碼呢?
本文試結合實例,簡述在業務流轉中對擁有公共屬性基類賦值的方法以及後續持續優化。
例子將省略公共屬性的判斷推算過程,實際項目中的公共屬性和業務對象過於複雜,自己簡單抽象對象如下(關註點為公共屬性的賦值)。
public class BaseStudentVO { private String stu_id; private String stu_name; private Integer stu_age; private Date stu_birthday; .......getter/setter } public class PrimaryScholar extends BaseStudentVO { .......logicAttribute }
BaseStudentVO 為抽取公共屬性的基類,業務中使用的為 PrimaryScholar,業務開始流轉時需要將基類的公共屬性填充進去。
起初編寫函數時採用反射方式,由子類反射獲取超類並迭代超類公共方法,獲取特定 set 方法進行調用,代碼思路如下:
private Object setBaseAtrByReflect(Map<String, Object> baseParam, Class clazz) throws Exception { Object instance = clazz.newInstance(); Class superclass = clazz.getSuperclass(); Method[] declaredMethods = superclass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { if (declaredMethod.getName().contains("set")) { if (declaredMethod.getName().contains("Stu_id")) { declaredMethod.invoke(instance, baseParam.get("stu_id")); continue; } if (declaredMethod.getName().contains("Stu_name")) { declaredMethod.invoke(instance, baseParam.get("stu_name")); continue; } if (declaredMethod.getName().contains("Stu_age")) { declaredMethod.invoke(instance, (Integer) baseParam.get("stu_age")); continue; } if (declaredMethod.getName().contains("Stu_birthday")) { declaredMethod.invoke(instance, (Date) baseParam.get("stu_birthday")); } } } return instance; }
起初這種方式大概是最差的,需要強轉類型/隨屬性增多的的判斷/迭代超類的所有的公共方法....簡直不忍直視。
後面空閑時間採用 java 內省重新編碼了函數, java 內省通俗來說是 jdk 提供給程式員一種更優雅的方式獲取 java-bean 的 getter/setter 方法。
內省和反射是兩碼事請區別對待,但毋容置疑內省是由反射實現,只不過代碼實現由 sun 公司的 java 團隊,並經過很嚴格測試。
private Object setBaseAtrByIntrospect(Map<String, Object> baseParam, Class clazz) throws Exception { Object instance = clazz.newInstance(); Class superclass = clazz.getSuperclass(); PropertyDescriptor propDesc; Method methodSetUserName; for (Field field : superclass.getDeclaredFields()) { propDesc = new PropertyDescriptor(field.getName(), clazz); methodSetUserName = propDesc.getWriteMethod(); methodSetUserName.invoke(instance, baseParam.get(field.getName())); } return instance; }
採用內省後比 assembleBaseAtrByReflect 反射時的代碼是不是清爽了很多,而且擯棄了很多難堪的地方,比如 強制類型轉換/隨屬性增多的判斷。
當後來偶然瞥見 apache.beanutil 中幾個有意思的 API 時,我覺得是時候優化下原項目中對應函數的編碼。
private Object setBaseAtrByBeanUtil(Map<String, Object> baseParam, Class clazz) throws Exception { Object instance = clazz.newInstance(); BeanUtils.setProperty(instance, "stu_id", baseParam.get("stu_id")); BeanUtils.setProperty(instance, "stu_name", baseParam.get("stu_name")); BeanUtils.setProperty(instance, "stu_age", baseParam.get("stu_age")); BeanUtils.setProperty(instance, "stu_birthday", baseParam.get("stu_birthday")); return instance; }
代碼是不是簡單明快了很多?看起來一目瞭然,而且進行了精準打擊,沒有多餘迭代判斷。
如果你反編譯 BeanUtil 中的對應方法,你還是會找到 內省的影子。
最近瞅見了分層中對泛型的抽象使用,難免讓人浮想聯翩,忍不住使用泛型調整了該函數的實現。
private <T extends BaseStudentVO> T setBaseAtrByGenenic(Map<String, Object> baseParam, T t) { t.setStu_id(baseParam.get("stu_id").toString()); t.setStu_name(baseParam.get("stu_name").toString()); t.setStu_age((Integer) baseParam.get("stu_age")); t.setStu_birthday((Date) baseParam.get("stu_birthday")); return t; }
泛型中的邊界限定很切合了該業務場景,使入參泛型 t 能順暢的使用基類中的 set 方法,徹底擺脫了反射。
反射的執行效率偏低,早已成為不爭的事實,使用泛型重寫後出現了類型強轉的問題,但這已經是最優解了。
從開始的反射到後面的內省到最終的泛型,想法都是在翻看其他人的分享或擼一段實現時突然蹦出來。