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