動態(tài)與彈性 細(xì)看編程語言的反射機(jī)制
因為可以在程序執(zhí)行期改變自身的動態(tài)語言近年越來越流行,在新一期的編程語言排行榜中,動態(tài)語言Ruby得到了穩(wěn)步提升。這篇文章將向您介紹動態(tài)程序設(shè)計語言的一個關(guān)鍵特性——反射機(jī)制。
在不更動已編譯好程序代碼的情況下,卻能大幅地影響程序的行為,這便是反射機(jī)制的動態(tài)威力所在許多接觸過像Java及C#這類程序語言的程序人,或許對"Reflection(反射)" 這個名詞不陌生,但實際上將這個技巧運作在日常開發(fā)工作的程序設(shè)計者,可能就并不那么多了。
"Reflection"這個名詞,在維基百科的解釋是“計算機(jī)程序用以觀察自身,及修改自身結(jié)構(gòu)和行為的過程”。事實上,透過反射技巧,程序在執(zhí)行時期本身便能夠得知自己的外觀長相,并且自我修改,甚至自我復(fù)制。
反射的作用:得知自己的外觀,甚至自我修改與復(fù)制
支持反射機(jī)制的程序語言眾多,大多數(shù)都是腳本式(Scripting Language)或是以虛擬機(jī)器為基礎(chǔ)的程序語言,例如Java、C#、Smalltalk、Python、Ruby、PHP、Perl等。甚至JavaScript也支持Reflection。
反射機(jī)制究竟能為程序提供什么樣的作用?為什么程序設(shè)計者需要動用到Reflection?針對諸如此類問題的答案,還是要回到為什么程序需要在執(zhí)行時期得知自己的外觀長相,甚至進(jìn)一步自我修改、復(fù)制。
相對于“執(zhí)行時期”,未使用反射機(jī)制的程序代碼,在編譯時期便已為編譯器所見。對這樣的對象導(dǎo)向程序而言,當(dāng)某個類別A存在與另一個類別B的互動時,類別B在編譯時期的長相,勢必已經(jīng)已為類別A所了解。
舉例來說,對于C++程序而言,類別A欲與類別B互動(例如呼叫它的函數(shù)),編譯器在編譯類別A的程序代碼時,必須也要能夠得知類別B的宣告及定義。相較于這樣的限制,Reflection則讓你的程序不必在編譯時期便確定此事,而是讓程序得以在執(zhí)行時期,根據(jù)一些外在的信息,決定操作的對象以及操作的方式,毋需于編譯時期便確定、同時寫死這些事情。
由此可以推想,反射機(jī)制是一個十分動態(tài)的特性,而且看起來可以為程序注入許多的彈性。
運用反射機(jī)制審視自身的特性
在解釋究竟反射機(jī)制能夠帶來什么好處之前,先來看看具體的Reflection機(jī)制,以明白透過常見的Reflection支持,在程序中究竟能做到那些事情。我以Java為例介紹,目的不在介紹Java完整的Reflection API,而是透過Java,幫助大家了解Reflection的一般性概念。
在Java中反射機(jī)制的源頭,就是一個叫“Class”的class(在C#中有一個相似的類別,則叫做Type)。這個類別有點特殊,原因在于此類別的每一個對象都用來表示系統(tǒng)中的每一個類別。
具體來說,每個Class對象都描述了每個類別的相關(guān)信息,也提供你透過它可以進(jìn)行的一些操作。想要開始Reflection的動作,就必須先取得Class類別的對象。最常被運用到的兩個途徑,一個便是Object(所有對象皆繼承的類別)所提供的getClass()函數(shù),另一個則是Class類別所提供的forName()靜態(tài)函數(shù)。
前者讓你得以取得一個對象(尤其是類型未知的對象)所屬的類別,而后者則讓你得以指定一個類別的名稱后,直接得到該類別對應(yīng)的Class對象。
有了Class對象之后,便能“審視”自身的特性,這些特性包括了它隸屬于那個Package、類別本身究竟是Public還是Private、繼承自那一類別、實作了那些接口等。更重要的是,你可以得知它究竟有那些成員變量以及成員函數(shù)(包括建構(gòu)式)
透過反射,不需在程序中明定函數(shù)名稱、自變量個數(shù)和類型
透過這個自我審視的過程,程序便能夠了解它所要處理的對象(尤其是類型未知的對象),究竟具備了什么特質(zhì)。對運用反射機(jī)制的程序而言,所了解到的這些特質(zhì),便會影響到該程序的運作行為。
取得了某類別的成員變量后(在Java中是以Field類別的對象表示),便可以取得該類別對象的成員變量值,也可以設(shè)定其值。同樣的,取得了某類別的成員函數(shù)后(在Java中是以Method類別的對象表示),便可取得該成員函數(shù)的回傳類型、傳入的自變量列表類型,當(dāng)然更重要的是,Method類別的對象,可被用以呼叫類別對象的相對應(yīng)成員函數(shù)。
所以假想一個情境,你的程序面臨了一個待處理的對象,但你完全不知道它是那個類型,有什么成員變量、有什么成員函數(shù),但你還是可以察覺出這一切,你會知道每個成員變量的名稱,每個成員函數(shù)的名稱、甚至你還可以取得每個成員函數(shù)的值、設(shè)定它們的值、還可以呼叫每個成員函數(shù),同時傳入正確的自變量、正確地取得回傳值。
除此之外,Java還允許程序人透過Class類別的newInstance()函數(shù),產(chǎn)生該類別的對象,或許是透過Constructor類別對象取得建構(gòu)式并呼叫、藉以執(zhí)行不同建構(gòu)式,以不同方式產(chǎn)生類別的對象。
從以上簡短的描述中,你應(yīng)當(dāng)能夠明白,Reflection讓你得以在執(zhí)行時期處理一些原先在編譯時期才能夠達(dá)成的動作。例如在Java中,你想要產(chǎn)生某個類別的對象,你得在程序中這么寫:
Foo obj = new Foo();
編譯時期就得將類別的名稱明確寫在程序中,也就是說,編譯時期就必須讓程序知道這件事。如果你想呼叫某個函數(shù),你得這么寫:
obj->bar(arg);
函數(shù)名稱、自變量個數(shù)和類型,都必須在程序代碼中明確指定
但有了反射,便不再需要在程序代碼中明確指定這些東西。例如,程序可以動態(tài)地決定究竟要產(chǎn)生那個類別的對象,你可以從設(shè)定檔中讀取類別的名稱、根據(jù)使用者的輸入值,經(jīng)過一段邏輯運算之后,決定要產(chǎn)生的類別名稱,接著再利用反射機(jī)制,產(chǎn)生類別的對象。你也可以動態(tài)地得知產(chǎn)生出來的對象擁有那些成員函數(shù),甚至是否具有特定名稱的成員函數(shù),接著呼叫這些函數(shù)。
有了反射,程序代碼在撰寫及編譯的時間點,毋需明白實際在運行時,究竟會涉及那些類別以及它們各自的行為。你所寫下的程序代碼,可以完全是對要處理的類別一無所知,也可以是對他們有一點基本的假設(shè)(例如要處理的類別都具有相同名稱的函數(shù),卻沒有實作相同的接口,或是繼承同樣的類別),一切都可以等到執(zhí)行時期,透過自我審視的能力,了解要面對的對象究竟具備什么特性,再依據(jù)相對應(yīng)的邏輯,動態(tài)利用程序代碼控制。 當(dāng)程序毋需將行為寫死,便消除了相依性
有了如此動態(tài)的能力,程序代碼在撰寫時毋需將行為寫死,包括要處理的類別、要存取的成員變量、要呼叫的函數(shù)等。這大大增加了程序彈性,同時也增加了程序的擴(kuò)充性。
舉例來說,一個連接數(shù)據(jù)庫的Java系統(tǒng)而言,在編譯時期是不需要知道究竟運作時會使用那一個JDBC驅(qū)動程序,系統(tǒng)只需要透過某種方式,例如在設(shè)定檔中指定類別名稱,那么程序便可以依據(jù)這類別名稱,加載相對應(yīng)的JDBC驅(qū)動程序,程序代碼中完全可以不涉及具體的JDBC驅(qū)動程序究竟為何。
這不僅消除了一定程度的相依性,相較于那些將數(shù)據(jù)庫連接程序代碼以靜態(tài)的方式附屬在程序代碼中的做法,一旦遇上了必須變更的時候,上述的作法只需更動JDBC驅(qū)動程序在設(shè)定檔中的名稱,毋需改變?nèi)魏我呀?jīng)編譯出來的程序代碼。
在不更動已編譯好程序代碼的情況下,大幅地影響程序的行為,便是反射機(jī)制的動態(tài)威力所在。
【編輯推薦】