最近做的項目常常涉及到架構,爲了提高代碼的複用性,需要在父類引用子類的方法。項目的服務器端用c#,客戶端使用java編程,都沒有方便明確的使用指針的方式(習慣c++的小夥伴對此哭了好幾回),所以都用了比較新的解決方法,對此做個小結。
在BO(business object)層如果有多個實體,常採用寫抽象類和繼承的方式實現代碼的複用。也就是你有一個抽象的父類AEntity有一些公共的方法和成員變量,子類有MyA,MyB,MyC分別對應數據庫的一張表,並且在子類還需要維護一個列表以映射子類成員和對應數據庫表的屬性。當BO層和DC(database control)要交互的時候,複用的代碼就要解決實體成員變量值到數據庫的交互問題。
如果分別在MyA,MyB,MyC中寫set,get代碼然後到AEntity裏調用再做必要的強轉會生成許多不必要的實體,至少代碼上看着是讓人不爽的(要有架構上的潔癖!),所以我們在寫C#的時候採取代理(delegate)的方式來獲取函數引用,從而在AEntity寫一個方法就能解決所有子類的需求。
如何實現C++指針那般的功能?首先在父類,聲明兩個代理類型:
public delegate void DelegateSetValue<T>(T t);
public delegate T DelegateReadValue<T>();
採用模板的原因是set,get的值可能是不同類型的,比如int,string等。
然後在子類初始化映射的時候,將代理實例化,也就是註冊需要代理的函數的指針,以MyA子類的ID屬性爲例,它由setID(int id),和getID()兩個方法操縱,所以在寫數據庫表A的屬性ID與子類成員映射時將兩個方法的指針註冊:
new object[]{"Int32",new DelegateSetValue<int>(setID),new DelegateReadValue<int>(getID),0,"ID"}
好的,現在我們在父類裏就可以對子類對象的成員變量賦值了,以查詢數據庫爲例:
//已經從DC獲取了查詢結果,rdr指向結果集第一行數據
//m_dicProperty2DBFieldMa維護數據庫到成員變量的映射列表
protected void LoadEntityFromDataReader(MySqlDataReader rdr)
{
object[] mapRow;
foreach(var item in m_dicProperty2DBFieldMap)
{
mapRow = item.Value;
switch (mapRow[0].ToString())
{
case "Int32":
(mapRow[1] as DelegateSetValue<int>)(Convert.ToInt32(rdr[(int)mapRow[3]]));
break;
case "String":
(mapRow[1] as DelegateSetValue<string>)(Convert.ToString(rdr[(int)mapRow[3]]));
break;
case "long":
(mapRow[1] as DelegateSetValue<long>)(Convert.ToInt64(rdr[(int)mapRow[3]]));
break;
case "enum":
(mapRow[1] as DelegateSetValue<int>)(Convert.ToInt32(rdr[(int)mapRow[3]]));
break;
}
}
}
開始mapRow是Object數組,其元素都是Object對象,所以我們強行轉換了mapRow[1]爲DelegateSetValue類型,並將rdr讀出來的ID值傳給setID方法,修改子類對象ID的值。
至此,我們實現了C#中讓父類調用子類函數的功能,那麼在Java中呢?目前有兩種思路,1.使用抽象類(或者接口更合理些)寫代理,2.使用java的反射機制,獲取子類的函數指針或者成員變量指針(感覺這個略強大。。。)
我們來研究一下第一種思路
定義一個接口,同樣使用模板類:
public interface Interface4SetGet<T> {
public abstract void SetValue(T data);
public abstract T GetValue();
}
子類們去實現這個接口:
m_Property2DBFieldMap.put("m_iID",new Object[]{"Integer",new Interface4SetGet<Integer>{
public void SetValue(int id);
},0,"id"});
注意Java的Map和c#的Dictionary結構對應
然後在父類加載子類成員變量與數據庫表的映射時,寫類似於如下的代碼:
//已從DC獲取查詢結果集,cur指向結果的第一行
protected void LoadEntityFromDataReader(Cursor cur)
{
Object[] mapRow;
for(Map.Entry<String,Object[]> entry : m_Property2DBFieldMap.entrySet())
{
mapRow = entry.getValue();
switch (mapRow[0].toString())
{
case "Integer":
((Interface4SetGet<Integer>)mapRow[1]).SetValue(cur.getInt((int) mapRow[2]));
break;
case "String":
((Interface4SetGet<String>)mapRow[1]).SetValue(cur.getString((int) mapRow[2]));
break;
case "long":
((Interface4SetGet<Long>)mapRow[1]).SetValue(cur.getLong((int) mapRow[2]));
break;
case "enum":
((Interface4SetGet<Integer>)mapRow[1]).SetValue(cur.getInt((int) mapRow[2]));
break;
}
}
}
由於子類映射初始化存的不再是函數指針,而是對象(接口的實現),所以調用這個對象的方法同樣可以達到修改子類成員變量的目的。
我們再來討論一下另外一種思路,使用反射
反射是什麼,這裏有個非常好的反射例子,如果是用反射得到method的指針,就和上面代理是一個道理了,主要來研究一下通過反射獲得Field指針的方法。
在子類MyA中定義需要反射的變量,並且定義這些變量與相應Field的映射
Map<String,Field> MemberName2Field = new HashMap<String,Field>();
MemberName2Field.put(item,User.class.getDeclaredField(item));
同理,如果成員變量要與數據庫交互,還需要將Field註冊到與數據庫交互的映射表裏
m_Property2DBFieldMap.put("m_sItem",new Object[]{"String",MemberName2Field.get("m_sItem"),1,"item"});
映射表的定義和之前一樣。
在AEntity就可以寫複用代碼了,還是以加載數據庫的數據爲例
//已從DC獲取查詢結果集,cur指向結果的第一行
protected void LoadEntityFromDataReader(Cursor cur)
{
Object[] mapRow;
for(Map.Entry<String,Object[]> entry : m_Property2DBFieldMap.entrySet())
{
mapRow = entry.getValue();
try {
switch (mapRow[0].toString())
{
case "Integer":((Field)mapRow[1]).set(this,cur.getInt(Integer.parseInt(mapRow[2].toString())));
break;
case "String": ((Field)mapRow[1]).set(this,cur.getString(Integer.parseInt(mapRow[2].toString())));
break;
case "long":((Field)mapRow[1]).set(this,cur.getLong(Integer.parseInt(mapRow[2].toString())));
break;
}
}catch (Exception ex)
{
Log.e(LOG_TAG,ex.toString());
}
}
}
Field自帶get,set方法,所以不用自己寫代理來完成函數指針的工作啦~~
討論暫時到此,分享請註明轉載出處哦!