摘要
開發者通過各種各樣的方法來嘗試避免單調冗余的編程
一些編程的規則例如繼承
多態或者設計模型可以幫助開發者避免產生多余的代碼
不過由於軟件開發方面存在著不確定性
因此這些規則並不能消除代碼維護和重新編寫的需要
在很多時候維護都是不可避免的
只有不能運作的軟件才是從不需要維護的
不過
這篇文章介紹了你可以使用Java的Reflection API的功能來減少單調的代碼編寫
並可以使用活動的代碼產生來克服reflection的限制
數據配置(由外部的源頭得到數據並且將它裝載到一個Java對象中)可以利用reflection的好處來創建一個可重用的方案
問題是很簡單的
將數據由一個文件裝入到一個對象的字段中
現在假設用作數據的目標Java類每星期改變一次?有一個很直接的解決方法
不過你必須不斷地維護載入的過程來反映任何的改變
在更復雜的環境下
同樣的問題可能會令系統崩潰掉
對於一個處理過運用XML的大型系統的人來說
他就會遇到過這個問題
要編寫一個載入的過程通常是非常單調乏味的
由於數據源或者目標Java類的改變
你需要經常更新和重新編寫代碼
在這裡我要介紹另一個解決方案
那就是使用映射
它通常使用更少的編碼
並且可以在目標Java類發生改變後更新自己
最初
我想介紹一個使用Reflection在運行期間配置數據的方案
在開始的時候
一個動態
基於映射的程序要比一個簡單的方法更有吸引力多了
隨後
我要揭示出運行時Reflection的復雜性和冒險性
這篇文章將介紹由運行時的Reflection到活動的代碼產生
由簡單到復雜
我的第一個方案使用一個載入類將數據從一個文件載入到對象中
我的源代碼含有對StringTokenizer對象下一節點方法的多次調用
在修改多次後
我的編碼邏輯變得非常的直接
系統化
該類構造了專用的代碼
在這個初始方案中
我只需要使用
個基本的對象
Strings
Objects
Arrays of objects
你可以影射類的對象來產生代碼塊
如下表所示
被影射來產生代碼塊的對象
我已經使用這個方案作了幾次編碼
因此我在寫代碼之前我已經知道該方案和代碼的結構
難點在於該類是變化的
類的名字
成份和結構在任何時候都可能發生變化
而任何的改變你都要重新編寫代碼
雖然會發生這些變化
但是結構和下載的流程仍然是一樣的
在寫代碼前
我仍然知道代碼的結構和成份
我需要一個方法
來將頭腦中的編碼流程轉換為一個可重用的
自動的形式
由於我是一個有效率的編程者
我很快就厭倦了編寫幾乎一樣的代碼
這時我想到了映射
數據配置通常需要一個源到目的數據的影射
影射可以是一個圖解
DTD(document type definition
文檔類型定義)
文件格式等
在這個例子中
映射將一個對象的類定義解釋為我們要映射的流程
映射可以在運行時復制代碼的功能
在需要重寫代碼時
我將載入的過程用映射來代替
它所需要的時間和重寫是一樣的
載入的工程可以概括為以下幾步
解釋
一個影射決定你在構造一個對象時需要些什麼
請求數據
要滿足構造的需要
要進行一個調用來得到數據
拖
數據由源中得到
推
數據被填充入一個對象的新實例
如果必要的話
重復步驟
你需要以下的類來滿足以上的步驟
.數據類(Data classes)
由ASCII文件中的數據實例化
類定義提供數據的影射
數據類必須滿足以下的條件
它們必須包含有一個構造器來接收全部必需的參數
以使用一個有效的狀態來構造對象
它們必須由對象構成
這些對象是reflective過程知道如何處理的
.對象裝載器(Object loader)
使用reflection和數據類作為一個影射來載入數據
產生數據請求
.載入管理器(Load manager)
作為對象裝載器和數據源的中介層
將對數據的請求轉換為一個數據指定的調用
這可以令對象載入器做到與數據源無關
通過它的接口和一個可載入的類對象通信
.數據循環接口(Data iterator interface)
載入管理器和載入類對象使用這個接口來由數據源中得到數據
一旦你創建了支持的類
你就可以使用以下的聲明來創建和影射一個對象
FooFileIterator iter = new FooFileIterator(fileLocation
log);
LoadManager manager = new FooFileLoadManager(iter);
SubFooObject obj =
(SubFooObject)ReflectiveObjectLoader
initializeInstance(SubFooObject
class
manager
log);
通過這個處理
你就創建了一個包含有文件內容的SubFooObject實例
局限
開發者必須決定使用哪個方案來解決問題是最好的
通常做出這個決定是最困難的部分
在考慮使用reflection作數據配置時
你要考慮到以下一些限制
不要令一個簡單的問題復雜化
reflection是比較復雜的
因此在必要的時候才使用它
一旦開發者明白了reflection的能力
他就想使用它來解決所有的問題
如果你有更快
更簡單的方案來解決問題時
你就不應該使用reflection(即使這個更好的方案可能使用更多的代碼)
reflection是強大的
但也有一些風險
考慮性能
reflection對性能的影響比較大
因為要在運行時發現和管理類屬性需要時間和內存
重新評估方案
如上所述
使用運行時reflection的第一個限制是
不要令簡單的問題復雜化
在使用reflection時
這是不可避免的
將reflection和遞歸結合起來是一個令人頭痛的問題
重新看代碼也是一件可怕的事情
而准確決定代碼的功能也是非常復雜的
要知道代碼的准確作用的唯一方法是使用一些取樣數據
逐行地看
就象運行時一樣
不過
對於每個可能的數據組合都使用這種方式幾乎是不可能的
在這種情況下
使用單元測試代碼可能有些幫助
不過也很可能出現錯誤
幸運的是
還有一個可選的方法
可選的方法
由上面列出的限制可以看到
在某些情況下
使用reflective載入過程可能是得不償失的
代碼產生提供了一個通用的選擇方法
你也可以使用reflection來檢查一個類並且為載入過程產生代碼
Andrew Hunt和David Thomas介紹了兩類的代碼產生器
見The Pragmatic Programmer(
/jwl#resources)
Passive(被動)
被動的代碼產生器在實現代碼時需要人工的干預
許多的IDE(集成開發環境)都提供相應的向導來實現
Active(主動)
主動的代碼產生指的是代碼一旦創建
就不再需要修改了
如果有問題產生
這個問題也應該在代碼產生器中解決
而不是在產生的源文件中解決
在理想的情況下
這個過程應該包含在編譯的處理過程中
從而確保類不會過期
代碼產生的優點和缺點包含有以下方面
優點
.簡單
產生的代碼通常是更便於開發者閱讀和調試
.編譯過程的錯誤
Reflexive在運行時出現錯誤的機會要比編譯的期間多
例如
改變被載入的對象將有可能令產生的載入類拋出一個編譯的錯誤
不過reflexive過程將不會看到任何的區別
直到在運行時遇到這個類
缺點
.維護
使用被動的代碼產生
修改被載入的對象將需要更新或者重新產生載入的類
如果該類被重新產生
那麼自定義的東西就會丟失
回頭再來看看主動代碼產生的好處
在這裡我們可以看到在運行時使用reflection是不可以接受的
主動的代碼產生有著reflection的全部好處
但是沒有它的限制
還可以繼續使用reflection
不過只是在代碼的產生過程
而不是運行的過程
理由如下
更少冒險
運行時的reflection明顯是更冒險的
特別是問題變得復雜的時候
基於單元測試
但並不是編譯器
多功能性
產生的代碼有著runtime reflection的全部好處
而且有著runtime reflection沒有的好處
更易懂
雖然經過多次的處理
但是將遞歸和reflection結合仍然是很復雜的
產生源代碼的方式更加容易解釋和理解
代碼產生過程需要遞歸和reflection
但得到的結果是可查看的源代碼
而不是難以理解的東西
寫代碼產生器
要寫一個代碼產生器
在思考的時候
你不能只是簡單地編寫一個方案來解決一個問題
你應該看得更遠
代碼產生器(以及reflection)需要你作更多的思考
如果你只是使用runtime reflection
你就不得不在運行時概念化問題
而不是使用簡單
兼容性好的源代碼來解決問題
代碼產生要求你從兩個方面來查看問題
代碼產生過程會將抽象的概念轉變為實際的源代碼
Runtime reflection則一直是抽象的
代碼產生過程將你的思考過程轉變為代碼
然後產生並且編譯代碼
編譯器會讓你知道你的思考過程在語法上是否正確
單元測試則可以驗證代碼在運行時的行為
就動態特性方面
runtime reflection就不能達到這個級別的安全性
代碼產生器
在經歷後幾次失敗的設計後
我認為最簡單的方法是
在載入過程中
為每一種需要實例化的類產生一個方法
一個方法工廠產生每個特別類的正確方法
一個代碼編譯對象緩沖來自代碼工廠的方法請求
以產生最終源代碼文件的內容
MethodCode對象是代碼產生過程的核心
以下就是一個int的代碼產生對象的例子
public class MethodForInt extends MethodCode {
private final static MethodParameter param = new MethodParameter(SimpleFileIterator
class
parser
);
public MethodForInt(Class type
CodeBuilder builder){
super(type
builder);
}
public MethodParameter[] getInputParameters(){
return new MethodParameter[]{
param
};
}
From:http://tw.wingwit.com/Article/program/Java/hx/201311/25959.html