本 developerWorks 文章系列完整介紹了如何用 Perl 進(jìn)行更佳編程。在本文(也是本系列的第五部分)中,Teodor 解釋了什么是面向?qū)ο缶幊?,何時使用它以及它是如何在 Perl 中工作的。面向?qū)ο缶幊蹋∣OP)是一種強(qiáng)大的編程技術(shù),但它不是萬能藥。優(yōu)秀的程序員必須理解如何使用它,并且必須知道何時依賴更傳統(tǒng)的編程技術(shù)。在 Perl 中使用 OOP 很簡單。與 C++ 和 Java 等限制性更強(qiáng)的 OOP 語言不同,Perl 中的 OOP 很少對程序員施加強(qiáng)制性約束。OOP 是對每一名程序員的工具箱的必要補(bǔ)充,并且是用于擴(kuò)展可用 Perl 解決的問題范圍的非常有用的技術(shù)。
OOP 是一種用于解決問題的編程方法或通用方法。與之相反,算法是用于解決特定問題的 特定方法。OOP 天生是一種強(qiáng)有力的方法;它往往使過程型和函數(shù)型編程方法與該問題較少相關(guān),并且除非它與過程型和函數(shù)型編程方法的混合對其極其有益,否則它們之間不會很好地結(jié)合在一起。在 Perl 中,這種強(qiáng)大的力量有所減弱,但仍然很好地存在著。
本文討論 Perl 中的 OOP 對比函數(shù)型和過程型編程的基本知識,并演示如何在 Perl 程序和模塊中使用 OOP。請記住,本文只是一篇概述,而不是對 Perl 中 OOP 所有方面的詳盡解釋。這樣的詳盡解釋需要幾本書才能講完,并且已經(jīng)被寫過幾次了。有關(guān)詳細(xì)信息,請參閱本文稍后的 參考資料。
OOP 是一種通過使用對象來解決問題的技術(shù)。在編程術(shù)語中,對象是這樣一些實(shí)體:它們的特性和行為是解決手頭問題所必需的。這個定義本應(yīng)該更詳細(xì)些,但是做不到,因?yàn)楫?dāng)今計算機(jī)行業(yè)中 OOP 方法的種類之多簡直難以想象。
在 Perl 編程環(huán)境中,OOP 并不是使用該語言所必需的。Perl 版本 5 和更高版本鼓勵使用 OOP,但確切地說不要求這樣做。所有 Perl 庫都是模塊,這意味著它們至少使用 OOP 的基本部分。而且,大多數(shù) Perl 庫都作為對象實(shí)現(xiàn),這意味著用戶必須通過良好定義的接口將它們作為具有特定行為和特性的 OOP 實(shí)體來使用。
![]() ![]() |
![]()
|
通常,有三個語言特性是 OOP 編程語言所必需的。它們是繼承、多態(tài)性和封裝。
Perl 支持繼承。當(dāng)一個對象(子對象)使用另一個對象作為起點(diǎn)(父對象),并且隨后在需要時修改其特性和行為時,就要用到 繼承。子-父關(guān)系是 OOP 所必需的,因?yàn)樗沟迷谄渌鼘ο蠡A(chǔ)上構(gòu)建對象成為可能。這種重用是使 OOP 成為程序員寵兒的好處之一。
有兩種繼承:單一繼承和多重繼承。 單一繼承要求子對象只有一個父對象,而 多重繼承更自由(與實(shí)際生活一樣,在編程過程中具有兩個以上的父對象會導(dǎo)致混亂并使子對象難以工作,因此,不要過多使用多重繼承)。盡管兩個或兩個以上的父對象實(shí)際上很少見,但 Perl 支持多重繼承。
多態(tài)性(來自希臘語,表示“很多形態(tài)”)是使一個對象被看成另一個對象的技術(shù)。這有點(diǎn)復(fù)雜,那么我舉個例子。比方說,您有一個綿羊牧場,里面有四只綿羊(綿羊?qū)伲悄鷦倓傎I了兩只山羊(山羊?qū)伲┖鸵恢坏聡裂蛉ㄈ迫畬伲?。您一共有多少動物?您得把所有的綿羊、山羊和狗加起來,結(jié)果是 7 只。其實(shí)您剛剛應(yīng)用了多態(tài)性,即為了計算,把三種不同種類的動物當(dāng)成一種通用類型(“動物”)對待。如果您把綿羊、山羊和狗當(dāng)成哺乳動物看待,這就是一個簡單的信心飛躍。生物學(xué)家每天都以這種方式使用多態(tài)性,而程序員則以從其它科學(xué)領(lǐng)域“竊用”(我是指“重用”)好主意聞名。
Perl 完全支持 多態(tài)性。但它用得不是很頻繁,因?yàn)?Perl 程序員看起來更喜歡用對象特性、而不是通過修改繼承的行為來修改通用行為。這意味著您更可能看到創(chuàng)建三個 IO::Socket::INET
對象的代碼:一個對象用于在端口 234 接收和發(fā)送 UDP 包、一個對象用于在端口 80 接收 TCP 包,還有一個對象在端口 1024 發(fā)送 TCP 包,而不大會看到對第一種情況使用 IO::Socket::INET::UDPTransceiver
、對第二種情況使用 IO::Socket::INET::TCPReceiver
而對第三種情況使用 IO::Socket::TCPTransmitter
的代碼。這就象是在生物學(xué)術(shù)語中說狗和山羊都是哺乳動物,但是山羊?qū)儆谏窖驅(qū)?,而狗屬于犬屬?
OOP 純化論者認(rèn)為每件事都應(yīng)該正確分類,但是 Perl 程序員根本不是純化論者。他們往往更不拘束于 OOP 規(guī)則,這使得他們在聚會中比 OOP 純化論者更快樂。
封裝指的是以這樣一種方式包含對象行為和特性:除非對象作者允許,否則用戶無法訪問該對象的行為和特性。在這種方式下,對象用戶無法做不準(zhǔn)他們做的事,無法訪問不準(zhǔn)他們訪問的數(shù)據(jù),并且通常是有害數(shù)據(jù)。Perl 通常用松弛的方法封裝。請參閱 清單 1。
![]() ![]() |
![]()
|
返回到我們最初的 OOP 如何是一種強(qiáng)有力的方法這一主題,我們現(xiàn)在可以看到 OOP 結(jié)合了幾個關(guān)鍵的概念,這使得它很難與過程型和函數(shù)型編程(PP 和 FP)混合使用。首先,PP 和 FP 都沒有繼承或類多態(tài)性的概念,因?yàn)樵?PP 和 FP 中根本就沒有類。在 PP 和 FP 中存在封裝,但只在過程型級別,從來不作為類或?qū)ο髮傩苑庋b。既然程序員不怕麻煩來使用這些基本的 OOP 工具,那就意味著程序員通常更可能對整個項(xiàng)目使用 OOP,而不是混合不兼容的方法。
有人可能爭論說所有程序最終都?xì)w結(jié)為指令的過程型執(zhí)行,因此無論 OOP 程序?qū)崿F(xiàn)得有多純,每個 OOP 程序都在其函數(shù)(也稱為方法)和創(chuàng)建第一個對象(該對象做其余工作)的代碼中包含過程型代碼。甚至象 Java 那樣接近“純”OOP 的語言都無法避免地需要一個 main()
函數(shù)。因此,看起來 OOP 只是 PP 的一個子集。但是這種 OOP 向序列指令的歸結(jié)和實(shí)際為每個操作所執(zhí)行的匯編程序指令一樣,都不是系統(tǒng)架構(gòu)設(shè)計師或程序員所關(guān)心的事。請記住,OOP 本身只是一種方法,而不是目的。
OOP 與過程型編程方法合作得不是很好,因?yàn)樗性趯ο笊?,而過程型編程基于過程(我們將 過程大致定義為不使用 OOP 技術(shù)就可以得到的函數(shù),而將 方法定義為只有在對象中才能得到的函數(shù))。正如方法一樣,過程只是由用戶調(diào)用的函數(shù),但是二者之間有一些差異。
過程不使用對象數(shù)據(jù)。必須在它們的參數(shù)列表中為它們傳遞數(shù)據(jù),或者它們必須使用所在作用域中的數(shù)據(jù)。過程可以訪問調(diào)用它時傳遞給它的任何數(shù)據(jù),甚至整個程序的全局?jǐn)?shù)據(jù)。方法應(yīng)該只訪問它們對象的數(shù)據(jù)。實(shí)際上,方法的函數(shù)作用域通常是包含該方法的對象。
常常發(fā)現(xiàn)過程使用全局?jǐn)?shù)據(jù),盡管只有在絕對必要時才應(yīng)該這樣做。應(yīng)該盡快重寫使用全局?jǐn)?shù)據(jù)的方法。過程通常用幾個參數(shù)調(diào)用其它過程。方法應(yīng)該只有幾個參數(shù),并且它們調(diào)用其它方法的次數(shù)比其它過程更多。
函數(shù)型編程(FP)與 OOP 配合不好有幾個原因。最重要的原因是 FP 基于用來解決問題的詳細(xì)函數(shù)型方法,而 OOP 則使用對象來表達(dá)概念,并且,與 OOP 方法只能在包含它們的對象中使用不同,F(xiàn)P 過程得到處使用。
綜上所述,我們現(xiàn)在可以解釋 Perl 為什么是混合 OOP、FP 和 PP 方法的最佳語言之一。
![]() ![]() |
![]()
|
Perl 是如何將 OOP 與過程型和函數(shù)型編程結(jié)合起來的?
Perl 是一種松弛的語言。它極力讓程序員以他們認(rèn)為方便的任何方式做他們想做的任何事。這與 Java 和 C++ 之類的語言截然不同。例如,如果程序員原本沒有聲明變量,Perl 樂于允許程序員自動創(chuàng)建變量(盡管不鼓勵這樣做,并且可以通過使用高度推薦的“use strict”編譯指示阻止)。如果您要向自己的腳開槍,Perl 會給您十發(fā)子彈和一個激光瞄準(zhǔn)鏡,然后站在一旁鼓勵您。
因此,Perl 是一種非常便于濫用方法的語言。別害怕。沒關(guān)系。例如,訪問內(nèi)部的對象數(shù)據(jù)、實(shí)時更改類和實(shí)時重定義方法都是允許的。Perl 方式是:允許程序員為了編碼、調(diào)試和執(zhí)行效率的目的而去打破規(guī)則。如果這有助于完成工作,那么沒關(guān)系。因此,Perl 本身可能是程序員最好的朋友,也可能是最壞的敵人。
如果混合 OOP、FP 和 PP 意味著打破規(guī)則,那么為什么任何人都想要混合 OOP、FP 和 PP 呢?讓我們回頭想想這個問題。什么是 OOP、FP 和 PP?它們只是現(xiàn)有的為編程團(tuán)隊(duì)服務(wù)的編程方法、概念集和規(guī)則集。OOP、FP 和 PP 是工具,每名程序員的首要工作就是要了解他的工具。如果一名程序員在排序散列時不能使用 FP 的 Schwartzian 變換,而是編寫他自己的 Sort::Hashtable
,或者不能重用 Sys::Hostname
模塊,而是編寫過程代碼來獲得系統(tǒng)主機(jī)名,那么這個程序員是在浪費(fèi)時間、精力和金錢,并且降低了代碼質(zhì)量和可靠性。
一個編程團(tuán)隊(duì)可能會因?yàn)樗鼈冏钍熘墓ぞ叨凑醋韵?,對它們來說,這可能正是最壞的事。如果一個團(tuán)隊(duì)只使用象計算機(jī)編程行業(yè)那樣令人沖動和充滿創(chuàng)新的行業(yè)中所保證的可用工具的一部分,那么它在幾年之后注定要變得毫無用處。程序員應(yīng)該能夠結(jié)合任何使工作更有效、代碼更好以及使團(tuán)隊(duì)更具創(chuàng)新能力的方法。Perl 認(rèn)可并鼓勵這種態(tài)度。
![]() ![]() |
![]()
|
OOP 的好處太多,本文難以列舉。正如我在前面提到的那樣,有很多關(guān)于該主題的書籍。這些好處中的一小部分是:易于代碼重用、代碼質(zhì)量的改進(jìn)、一致的接口和可適應(yīng)性。
因?yàn)?OOP 建立在類和對象的基礎(chǔ)之上,所以重用 OO 代碼意味著在需要時只需簡單地導(dǎo)入類。至今為止,代碼重用是使用 OOP 的唯一最主要原因,也是 OOP 在當(dāng)今業(yè)界中的重要性和流行性日益增加的原因所在。
這里有一些陷阱。例如,在當(dāng)前的情況下,以前問題的解決方案可能不理想,并且文檔庫編制得很差,以至于理解和使用文檔編制很差的庫所花的時間可能與重新編寫庫的時間一樣長。系統(tǒng)架構(gòu)設(shè)計師的工作是看到和避免這些陷阱。
使用 OOP 可以提高代碼質(zhì)量,因?yàn)榉庋b減少了數(shù)據(jù)毀壞(“友好之火”),而繼承和多態(tài)性則減少了必須編寫的新代碼數(shù)量和復(fù)雜性。在代碼質(zhì)量和編程創(chuàng)新之間有一個微妙的平衡,這最好留給團(tuán)隊(duì)去發(fā)現(xiàn),因?yàn)樗耆Q于團(tuán)隊(duì)的構(gòu)成和目的。
OOP 繼承和重用使得在代碼中實(shí)現(xiàn)一致的接口變得簡單,但是并不能說所有的 OO 代碼都有一致的接口。程序員仍然必須遵循通用的體系結(jié)構(gòu)。例如,團(tuán)隊(duì)?wèi)?yīng)該在錯誤日志記錄的格式和接口方面達(dá)成一致,最好通過一個允許日后擴(kuò)展并且極其易用的示范模塊接口來這樣做。只有在那時,每名程序員才能承諾使用該接口,而不是無規(guī)則的 print 語句,因?yàn)樗麄儠J(rèn)識到在出現(xiàn)下一個錯誤日志記錄函數(shù)時,學(xué)習(xí)該接口的努力不會白費(fèi)。
可適應(yīng)性在編程中是一個有些含糊的概念。我愿意把它定義成對環(huán)境和用法更改的接受性和預(yù)見性。對于編寫良好的軟件來說,可適應(yīng)性很重要,因?yàn)樗械能浖仨氹S著外部世界而進(jìn)化。編寫良好的軟件應(yīng)該很容易進(jìn)化。OOP 通過模塊設(shè)計、改進(jìn)的代碼質(zhì)量和一致的接口確保新操作系統(tǒng)或者新報告格式不要求對體系結(jié)構(gòu)的核心作出根本更改,從而有助于軟件的進(jìn)化。
![]() ![]() |
![]()
|
不管您是否相信,Perl 中的 OOP 對初級和中級用戶都不難,甚至對高級用戶也沒那么復(fù)雜。根據(jù)我們到目前為止所討論的有關(guān) OOP 的復(fù)雜工作方式,您可能不這么認(rèn)為。然而,Perl 卻樂意對程序員施加盡可能少的限制。Perl OOP 就象烤肉(恕我比喻不當(dāng))。每個人都帶來自己的肉,并以自己喜愛的方式烤肉。甚至還有烤肉的團(tuán)隊(duì)精神也是那樣,就象可以輕易在不相關(guān)的對象之間共享數(shù)據(jù)一樣。
我們必須采取的第一步是理解 Perl 包。包類似于 C++ 中的名稱空間和 Java 中的庫:象用來將數(shù)據(jù)限制在特定區(qū)域的圍欄。然而,Perl 包只是為程序員提供建議。缺省情況下,Perl 不限制包之間的數(shù)據(jù)交換(盡管程序員可以通過詞法變量這樣做)。
|
請注意,可以在所有三個包(“main”、“Pig”和“Cow”)中訪問文件作用域內(nèi)的詞法變量 $extra_sound
,因?yàn)樵谠撌纠兴鼈兪窃谕晃募卸x的。通常,每個包在它自己文件內(nèi)部定義,以確保詞法變量為該包所私有。這樣就可以實(shí)現(xiàn)封裝。(有關(guān)詳細(xì)信息,請運(yùn)行“ perldoc perlmod
”。)
接下來,我們要將包與類關(guān)聯(lián)。就 Perl 而言,類只是一個奇特的包(相反,對象由 bless()
函數(shù)特別創(chuàng)建)。同樣,Perl 對 OOP 規(guī)則實(shí)施得不是很嚴(yán)格,以便程序員不為其所約束。
new()
方法是類構(gòu)造器的慣用名稱(盡管按照 Perl 慣有的不嚴(yán)格方式,您可以使用任意名稱)。當(dāng)將類實(shí)例化成對象時都要調(diào)用它。
|
可以通過將清單 2 中的代碼放入任何目錄內(nèi)名為 Barebones.pm 的文件中,然后在該目錄中運(yùn)行以下命令來測試該代碼(這表示:“在庫路徑中包括當(dāng)前目錄,使用 Barebones 模塊,然后創(chuàng)建一個新的 Barebones 對象”):
|
例如,可以在 new()
方法中放入 print 語句,以便看到 $classname
變量所擁有的內(nèi)容。
如果調(diào)用 Barebones::new()
而不是 Barebones->new()
,類名將不會傳遞到 new()
。換句話說, new()
將不作為構(gòu)造器,而只是普通的函數(shù)。
您可能要問:為什么需要傳入 $classname
。為什么不直接用 bless {}, "Barebones";
?因?yàn)槔^承的緣故,這個構(gòu)造器可能被一個從 Barebones
繼承、但名稱卻不是 Barebones
的類調(diào)用。您可能正在用錯誤的名稱享有錯誤的事,而在 OOP 中,那是個壞主意。
除了 new()
之外,每個類都需要成員數(shù)據(jù)和方法。定義它們就象編寫幾個過程一樣簡單。
|
可以用以下命令測試該代碼:
|
您應(yīng)該得到 ‘2‘ 這個結(jié)果。構(gòu)造器被調(diào)用兩次,它修改詞法變量( $count
),該變量被限制在 Barebones
包的作用域,而 不是每個 Barebones
對象的作用域。應(yīng)該將對象本身范圍內(nèi)的數(shù)據(jù)存儲在對象本身中。在 Barebones
的示例中,被享有成對象的是匿名散列。請注意我們怎樣才能在每次調(diào)用該對象的方法時訪問該對象,因?yàn)閷υ搶ο蟮囊檬莻鬟f給那些方法的第一個參數(shù)。
有幾個特殊的方法,例如 DESTROY()
和 AUTOLOAD()
,Perl 在某些條件下會自動調(diào)用它們。 AUTOLOAD()
是用來允許動態(tài)方法名稱的全捕獲(catch-all)方法。 DESTROY()
是對象析構(gòu)器,但是除非您確實(shí)非常非常需要,否則不應(yīng)該使用它。在 Perl 中使用析構(gòu)器通常表明您還在象 C/C++ 程序員那樣考慮問題。
讓我們看一下繼承。在 Perl 中通過更改 @ISA
變量來這樣做。您只需將一個類名表賦值給該變量即可。就是這樣。您可以在 @ISA
中放入任何東西。您可以使您的類成為 Satan
的子類。Perl 不在乎(盡管您的牧師、部長、教長、猶太學(xué)者等可能在乎)。
|
這些是 Perl 中 OOP 的最基本知識。Perl 語言中還有很多您應(yīng)該探索的知識。人們已經(jīng)撰寫了很多有關(guān)這一主題的書籍。如果想閱讀的話,請參考 參考資料。
![]() ![]() |
![]()
|
您不想擁有一個可以為您編寫 Perl 類、還可以編寫文檔(POD)框架并且通??梢酝ㄟ^正確地完成這些事而使您的生活輕松一些的工具嗎?Perl 正好帶有這種工具: h2xs
。
別忘了使用幾個重要標(biāo)志:“ -A -n Module
”。利用這些標(biāo)志,h2xs 將生成一個名為“Module”、且里面全是有用文件的框架目錄。這些文件是:
Module.pm
,模塊本身,帶有已經(jīng)編寫好的框架文檔。 Module.xs
,用于將您的模塊與 C 代碼鏈接。(有關(guān)詳細(xì)信息,請運(yùn)行“ perldoc perlxs
”。) MANIFEST
,用于打包的文件列表。 test.pl
,框架測試腳本。 Changes
,對該模塊所做更改的日志。 Makefile.PL
,makefile 生成器(用“ perl Makefile.PL
”運(yùn)行它。) 您無需使用所有這些文件,但是在您確實(shí)需要它們時知道它們在那里是很好的。