引自: iThome http://www.ithome.com.tw/itadm/article.php?c=57510

運用Reflection機制,能夠讓程式人不用在程式碼中寫死(Hardcode)所要處理的類別、資料成員以及函式。當程式人在程式碼中寫死了這些資訊,便代表程式碼和這些資訊有了相依性,那麼當這些事情必須改變的時候,便會對程式碼造成衝擊,迫使程式人必須修改程式碼。

有了Reflection機制,這些資訊都可以獨立於程式碼之外,系統可以透過各式各樣其他的途徑,例如使用者的輸入或是外部設定檔,取得這些資訊。

若要施加限制,可以彈性設定「動態」的容忍範圍
Reflection機制消除了程式碼與所用類別間的相依性,這使得系統得以動態地改變所欲運用的類別。即使需求有所改變,只要需求變化的範圍,仍在我們所設計的動態容忍範圍內,那麼只需要換上新的類別,便能夠滿足需求,毋需修改既有的程式碼。

有時候,僅僅只是避免寫死類別名稱還不夠彈性,因為這種情況,仍然必須寫死函式或資料成員的名稱。有些情況,我們是故意只避免寫死類別名稱,因為我們想要施加其他的限制,例如在前一回中所提到,我們檢查產生出來的類別,是否實作javax.servlet.Servlet介面,倘若實作了,便進一步進行轉型。

所以,在這種設計底下,雖然不寫死類別名稱,但是在程式碼中,仍然對於所要運用的類別,有一定的假設及限制。這便是「動態容忍範圍」。

更大的動態容忍範圍,可驅動更多類型的類別
不過,有些應用下,我們希望能夠不寫死所欲運用類別的資料成員名稱或函式名稱,以便讓寫下的程式碼,能夠有更大的動態容忍範圍,以便驅動更多類型的類別。

舉一個例子來說,對Java的程式人來說,常常會利用像JavaBean的概念實作資料模型。例如,我用一個JavaBean類別表示資料庫中某個特定表格的資料,這使得我們可以利用像操作Java物件的方式來操作資料。

何謂JavaBean呢?要成為JavaBean並不需要繼承自特定的基礎類別,它最重要的條件之一,是要提供若干個「getter」以及「setter」函式表示它所具備的屬性(Properties)。

我們可以反過來說,當某JavaBean類別具備這樣子的getter函式或setter函式時,該JavaBean類別就具有這個屬性。屬性並不是Java語言內建的機制(像C#便內建),但利用getter/setter函式的定義,使得我們可以定義出屬性的概念。

利用JavaBean的屬性概念,我們可以讓每個JavaBean類別的物件,都能夠表示某個表格的一筆資料列資料,而該JavaBean物件的每個屬性,便都對應到該表中的每個資料欄。

例如某個叫做article的表格有seqNo、title、description的資料欄位,我們便可以設計一個Article的類別,有getSeqNo()、setSeqNo()、getTitle()……等函式,代表它分別有seqNo、title、description等屬性。

當我們想要這麼做時,免不了需要幾種程式,包括:將資料庫中的一列資料轉換成為一個相對應表示該表格資料的Java物件,透過該物件執行新增、修改、刪除、查詢,並且同時反映回資料庫中。

當JavaBean必須存取任意資料表,就不能在程式中寫死函式
但是,在這個應用中,之前運用在Serlvet載入及對應的技巧就用不上了,因為在這個技巧中,我們雖然不限制類別的名稱,但是限制它必須都有一個共通的介面,以便我們向上轉型,並且呼叫特定的函式。

對於利用JavaBean類別對映至資料庫表格的應用來說,由於必須支援任意的資料庫表格,而每個表格都有可能有不同名稱、不同型別的資料欄,這使得JavaBean類別可能會有各式各樣的屬性,它們的名稱也都不固定。為此,便會衍生出各種可能的getter/setter函式名稱。

因為函式名稱不盡相同,所以也不可能定義一個基礎類別或共通的介面涵蓋所有的可能。為此,我們得做到「不在程式碼中寫死函式名稱」才行。

類別名稱以及函式名稱均可來自程式以外的設定
Reflection機制不僅允許程式人在程式碼中不寫死函式名稱,但又能呼叫該函式,同時還讓程式人能夠查出任意物件的所有函式。針對資料庫與JavaBean的對映應用來說,你可以利用一個XML檔案(或其他方式),建立JavaBean類別名稱與資料庫表格名稱之間的關係,那麼當程式人想要載入某個資料庫表格中的資料時,便可以得知此刻要處理的究竟是那一個類別(當然,類別名稱並不寫死在程式碼中。對程式碼來說,類別名稱是讀取設定而來的),接著便可以動態地運用Reflection的技巧,產生該類別的物件。

產生完物件後,下一步便是要將資料庫表格中的資料填入物件中。對使用JDBC的Java程式人而言,可能得到的是一個ResultSet物件,透過ResultSet物件,程式人可以得到ResultSetMetaData物件,便可以藉此查出所有的資料欄名稱及型別。

我們的目的是要將ResultSet中的資料設至JavaBean物件中,有了每一個資料欄的名稱及型別後,便可以利用Class物件的getDeclaredMethod(String name, Class[] parameterTypes),找出相匹配、具有對應名稱(倘若資料欄為title,那麼setter名稱便為setTitle)及引數列表型別(倘若title型別為字串型別,那麼引數列表中的Class[]便是僅有String之Class物件元素)的函式。

接著,再取出ResultSet中每個資料欄的值,便可以利用getDeclaredMethod()所回傳的Method物件,去呼叫Method.invoke(),那麼便能夠順利呼叫物件的setter函式,將值設至物件之中。
上述所描述的過程,是將一個ResultSet物件所含之值,轉換成為對應的JavaBean物件的值。你應該會留意到,依照這個方法所寫下的程式碼,本身對JavaBean類別名稱、以及JavaBean會具有那些屬性,一無所知。

這是必然的,因為當這段程式碼想處理所有可能的資料庫表格以及JavaBean類別時,就必定不會在程式碼中寫下它所能支援的類別及屬性名稱(也就是getter/setter函式名稱)。我們所要面對的類別名稱以及函式名稱,都是來自於程式碼以外的部分,例如XML設定檔,或者是資料庫回傳的資料。

程式既小又單純,且能發展更多可能性
雖然程式碼完全不涉及這些名稱,卻仍然能夠產生所需的類別,並呼叫所需的函式,這正是Reflection的作用所在──程式人可以將產生類別物件的邏輯、呼叫函式的邏輯,部分地移至程式碼之外。

而這些邏輯可以是在外部設定(例如XML設定檔)、也可以是由其他系統的輸出結果(例如資料庫系統回傳的Result的Meta Data)來決定。

這使得我們所寫下的程式碼既小且單純,又能驅動許多的可能,這正是典型使用Reflection技巧的程式碼的特色。

JoJoSun 發表在 痞客邦 PIXNET 留言(0) 人氣()