利用Reflection的動態威力,能夠大幅增加程式的擴充性及彈性,因為程式人可以在不更動已編譯程式碼的情況下,變動系統的行為。

Reflection可以做的神奇事很多,例如動態針對未知的物件,查出它擁有的資料以及函式成員,甚至即使程式碼完全沒有寫入任何關於這個函式的名稱,也能動態呼叫查詢出來的函式。

善用Reflection,讓系統的擴充性和彈性有長足的進步
大多數人初次接觸Reflection都大感神奇,但是接下來呢?這樣神奇的機制能為程式設計工作帶來什麼好處呢?

首先,來回想Reflection的機制能夠幫助我們做到那些事情?(1)即使在程式碼中沒有寫下類別的名稱,也能產生該類別的物件。(2)即使在程式碼中對所要存取物件的類別一無所悉,也能夠知道它究竟擁有那些資料成員、函式,以及這些資料成員及函式的相關屬性。(3)即使在程式碼中沒有寫下某物件的資料成員名稱,也能夠存取該資料成員。(4)即使在程式碼中沒有寫下某物件的函式名稱,也能夠呼叫該物件的特定函式。

以上Reflection機制能夠發揮的用處,僅僅只是表面上的陳述,對此,更深層的意義應該是──Reflection機制能夠讓你毋需在程式碼中寫死所要處理的類別、資料成員以及函式。

著名的四人幫所著的《Design Patterns: Elements of Reusable Object-Oriented Software》一書中提到,引發重新設計的共通導因有:寫死類別名稱、寫死函式名稱、鎖在特定的硬體及軟體平臺、相依特定的實作、綁在特定演算法上……等。

倘若類別名稱可以不寫死,而是從另一個途徑,例如設定檔中取得,再於程式之中運用這個類別,那麼便可以很方便地更換程式中所要運用的類別。

而「可以更換所要運用的類別」意義很大,因為每一個類別都可能代表一種行為的模式,當你毋需變更程式碼,卻可以透過設定的方式,改變程式中所要運用的類別,可以想像這代表著擁有很大的擴充性及彈性。

從Reflection的角度看Web應用伺服器設計
在前文中所提到的JDBC驅動程式便是一個很好的例子。Java的資料庫應用程式,並不需要將欲採用的JDBC驅動程式類別名稱寫死在程式碼中,相反的,它可以將JDBC驅動程式的類別名稱寫在設定檔中,透過動態指定類別名稱的方式載入JDBC驅動程式。所以想要動態更換所使用的JDBC驅動程式,一點都不難。

另一個典型的例子是Java在伺服器端的Servlet運作方式。倘若你是一個Java的Web開發者,那麼對於撰寫Java Servlet程式時,必須於web.xml中設定要掛上Web應用程式的Servlet程式類別名稱肯定不陌生。你有沒有思考過,為什麼它是這麼設計的?這麼設計有什麼好處?

你可以從Web應用伺服器的角度來思考這件事。倘若你是Web應用伺服器的設計者,對於應用程式開發者究竟會在Web應用程式中,寫下多少Servlet程式,完全一無所知,所以程式中自然無法寫死這件事情。

你沒有辦法在應用伺服器的程式碼中寫下產生某個Servlet類別物件的程式碼,更甭提執行這個物件的函式了。假如有了Reflection機制,上述就一點都不難了。

就如同Java Web應用程式的設計一樣,你可以允許開發者將想要掛上Web應用程式的Servlet類別名稱設定於XML設定檔中,因此,伺服器便可以透過設定檔,得知開發者究竟要掛上那些Servlet類別的名稱。因為有Reflection,所以你可以動態給定一個類別名稱後,產生該類別的物件。對Java程式來說,或許像是這樣(當然是十分簡化的寫法,純粹為了說明觀念):

Class clz1 = Class.forName(“javax.servlet.Servlet”);
Class clz2 = Class.forName(serlvetClassName);
if( !clz1.isAssignableFrom(clz2) )
{
ˉ// 所載入類別非Serlvet類別,進行錯誤處理
}
Servlet servlet = (Servlet) clz2.newInstance();

以上的程式碼中,首先利用Class.forName()分別取得代表「javax.servlet.Servlet」以及所欲載入的Servlet類別的Class物件。接著利用Class的isAssignableFrom()函式檢查所欲載入的Servlet類別,是否為「javax.servlet.Servlet」這個類別的衍生類別,倘若不是,代表應用程式設計者想掛上一個根本就不是Servlet程式的程式,自然應當加以拒絕,反之,便可以利用Class物件的newInstance()函式,產生開發者所指定的Servlet物件。

由於已經檢查過所載入的類別物件會是Serlvet類別的衍生類別,因此,可以安心地進行轉型至Servlet類別的動作,最後便可以在伺服器程式中運用這個完全陌生的類別物件。

利用這樣子的寫法,程式碼完全不知道、也不需要知道究竟會面對多少個Servlet類別,但它有能力產生任意指定類別名稱的物件,並且加以運用,甚至可以進一步檢查所指定的類別,是否滿足它所希望施加的唯一限制便是:此類別必須擁有javax.servlet.Servlet介面才行。

對於一個應用程式伺服器而言,它扮演的是核心的角色,本身並不寫死太多的資訊,因為一旦寫死資訊,便會限制能夠應用的範圍。當我們用上述的方式讓開發者自行掛上Servlet程式時,就等於在擴充整個應用系統的能力。

對Factory設計模式來說,Reflection也提供很大的助益
雖然上述是利用Java Web應用程式伺服器載入Servlet類別的方式來說明,而程式人的絕大多數,並非Java Web應用程式伺服器的設計者,但是這樣的技巧,仍可以運用在自己開發的其他系統上。

當你希望所運用的類別是可以透過外部設定時,這樣的技巧特別有用。例如,系統中可以具備Plug-In的觀念,每個Plug-In都是獨立的類別,對系統而言,並不知道究竟會掛上那些Plug-In,但透過設定的方式,允許使用者或其他程式設定Plug-In的類別名稱;而系統便可藉由讀取設定得知有那些Plug-In,再利用Reflection技巧,便可以將這些Plug-In一一載入,就好像Web應用程式伺服器載入Servlet一樣。

所以,你應當能明白到,利用Reflection技巧產生類別物件的最大好處,是能夠不在程式碼寫死類別名稱,卻又產生該類別的物件。這對Factory族系的設計模式來說,提供十分大的助益。

Factory族系的設計模式之所以被發明出來,便是為了要用戶端程式碼隱藏確切產生的類別名稱,僅讓用戶端應用程式以介面相待。倘若沒有Reflection機制,的確還是能夠對用戶端程式碼隱藏住類別名稱,但是,Factory類別本身仍然相依於此類別的名稱(也就是說,必須在Factory類別裡寫死類別名稱),某種程度這已將資訊隱藏而且封裝。

若能運用Reflection,連Factory類別本身都能去除對類別名稱的相依性,它只需要讀取程式碼的外部設定,並且利用Reflection機制產生物件即可。

不難看出,Reflection能對「去除程式碼對類別名稱的相依性」產生很大作用。去除相依性,你所得到的,便是擴充性及彈性。

專欄作者

熱門新聞

Advertisement