官方手冊:http://perldoc.perl.org/perlobj.htmlhtml
本系列:數組
第3篇依賴於第2篇,第2篇依賴於1篇。數據結構
3種動物牛Cow、羊Sheep、馬Horse發出的聲音各不相同。在lib目錄下建立三個各自的文件,分別定義它們的叫聲子程序:ide
lib/Cow.pm中:工具
#!/usr/bin/env perl use strict; use warnings; package Cow; sub speak { print "a Cow goes moooo!\n"; } 1;
lib/Sheep.pm中:編碼
#!/usr/bin/env perl use strict; use warnings; package Sheep; sub speak { print "a Sheep goes baaaah!\n"; } 1;
lib/Horse.pm中:code
#!/usr/bin/env perl use strict; use warnings; package Horse; sub speak { print "a Horse goes neigh!\n"; } 1;
而後定義一個文件speak.pl,使用這3個模塊,分別調用這3個子程序:htm
#!/usr/bin/env perl use strict; use warnings; use lib "lib"; use Cow; use Sheep; use Horse; Cow::speak(); Sheep::speak(); Horse::speak();
上面使用包名的徹底限定方式調用子程序(或訪問其它屬性),這實際上是有必定限制的,好比沒法直接使用包名做爲變量:對象
foreach my $who (qw(Cow Horse Sheep)){ $who::speak(); # 這是錯的 eval "$who"."::speak()"; # 這是正確的 }
上面經過eval的二次解析功能,先將變量$who
替換,而後再調用對應的方法。blog
但這種寫法無比醜陋。可使用另一種訪問其它包中的子程序(或其它屬性):瘦箭頭。
foreach my $who (qw(Cow Horse Sheep)){ $who->speak(); }
其實這是面向對象的調用方式。經過這種方式調用其它包的子程序,傳遞給子程序的第一個參數將老是對象名或類名。在Perl中,類就是包,因此,下面幾個調用方式是等價的:
瘦箭頭調用方式 徹底限定包名調用方式 --------------------------------------------------- Cow->speak(args) == Cow::speak('Cow',args) Sheep->speak(args) == Sheep::speak('Sheep',args) Horse->speak(args) == Horse::speak('Horse',args)
所以,當使用瘦箭頭調用子程序的方式時,若是這個子程序須要處理參數,必需要考慮隱含的第一個參數。因此,修改lib/{Cow,Sheep,Horse}.pm
中的speak()子程序:
lib/Cow.pm中:
#!/usr/bin/env perl use strict; use warnings; package Cow; sub speak { my $class = shift; # 將第一個參數保存起來 print "a $class goes moooo!\n"; # 插入第一個參數的變量 } 1;
lib/Sheep.pm中:
#!/usr/bin/env perl use strict; use warnings; package Sheep; sub speak { my $class = shift; # 將第一個參數保存起來 print "a $class goes baaaah!\n"; # 插入第一個參數的變量 } 1;
lib/Horse.pm中:
#!/usr/bin/env perl use strict; use warnings; package Horse; sub speak { my $class = shift; # 將第一個參數保存起來 print "a $class goes neigh!\n"; # 插入第一個參數的變量 } 1;
這樣一來,將硬編碼的Cow、Sheep和Horse使用共同的$class
進行替換,增長了speak()的可移植性和共性,從而爲面向對象的代碼複用帶來便捷性。
所謂的類,就像是一個模板;所謂對象,就像是經過模板生成的具體的事物。類通常具備比較大的共性,對象通常是具體的,帶有本身的特性。
類與對象的關係,例如人類和人,鳥類和麻雀,交通工具和自行車。其中人類、鳥類、交通工具類都是一種類型稱呼,它們中的任何一種都具備像模板同樣的共性。例如人類的共性是能說話、有感情、雙腳走路、能思考等等,而根據這我的類模板生成一我的,這個具體的人是人類的實例,是一我的類對象,每個具體的人都有本身的說話方式、感情模式、性格、走路方式、思考能力等等。
類與類的關係。有的類的範疇太大,模板太抽象,它們能夠稍微細化一點,例如人類能夠劃分爲男性人類和女性人類,交通工具類能夠劃分爲燒油的、電動的、腳踏的。一個大類按照不一樣的種類劃分,能夠獲得不一樣標準的小類。不管如何劃分,小類老是根據大類的模板生成的,具備大類的共性,又具備本身的個性。
在面向對象中,小類和大類之間的關係稱之爲繼承,小類稱之爲子類,大類稱之爲父類。
類具備屬性,屬性通常包括兩類:像名詞同樣的屬性,像動詞同樣的行爲。例如,人類有父母(parent),parent就是名詞,人類能吃飯(eat),eat這種行爲就是動詞。鳥類能飛(fly),fly的行爲就是動詞,鳥類有翅膀(wing),wing就是名詞。對於面向對象來講,名詞就是變量,動詞行爲就是方法(也就是子程序)。
當子類繼承了父類以後,父類有的屬性,子類能夠直接擁有。由於子類通常具備本身的個性,因此子類能夠定義本身的屬性,甚至修改從父類那裏繼承來的屬性。例如,人類中定義的eat屬性是一種很是抽象的、共性很是強的動詞行爲,若是女性人類繼承人類,那麼女性人類的eat()能夠直接使用人類中的eat,也能夠定義本身的eat(好比淑女地吃)覆蓋從人類那裏繼承來的eat(沒有形容詞的吃),女性人類還能夠定義人類中沒有定義的跳舞(dance)行爲,這是女性人類的特性。子類方法覆蓋父類方法,稱之爲方法的重寫(override),子類定義父類中沒有的方法,稱爲方法的擴展(extend)。
不管是對象與類仍是子類與父類,它們的關係均可以用一種"is a"來描述,例如"自行車 is a 交通工具"(對象與類的關係)、"筆記本 is a 計算機"(子類與父類的關係)。
爲了構造更通用的speak,將它們的不一樣點抽取出來:動物名稱、叫聲。
動物名稱這裏和類名(包名)相同,前面已經替換成了共同的$class
,動物的叫聲是隨動物種類不一樣而不一樣的,沒法直接實現它們的共性。但能夠定義一個輔助性的同名子程序sound(),用來返回各類動物的叫聲:
lib/Cow.pm中:
#!/usr/bin/env perl use strict; use warnings; package Cow; sub sound { "moooo"; } sub speak { my $class = shift; print "a $class goes ",$class->sound(),"!\n"; } 1;
lib/Sheep.pm中:
#!/usr/bin/env perl use strict; use warnings; package Sheep; sub sound { "baaaah"; } sub speak { my $class = shift; print "a $class goes ",$class->sound(),"!\n"; } 1;
lib/Horse.pm中:
#!/usr/bin/env perl use strict; use warnings; package Horse; sub sound { "neigh"; } sub speak { my $class = shift; print "a $class goes ",$class->sound(),"!\n"; } 1;
如此一來,lib/{Cow,Horse,Sheep}.pm
中的全部speak()子程序都徹底相同。
顯然,將這3個類中的共同部分抽取出來放進一個通用的模塊中進行復用更好。
例如,放進lib/Animal.pm模塊文件中:
#!/usr/bin/env perl use strict; use warnings; package Animal; sub speak { my $class = shift; print "a $class goes ",$class->sound(),"!\n"; } sub sound { die 'You have to define sound() in a subclass'; } 1;
爲了讓Cow、Horse、Sheep直接使用Animal中的speak()子程序,須要讓Cow、Horse、Sheep去繼承Animal。其中Animal類稱爲父類(base class/super class/parent class),Cow、Horse、Sheep稱爲子類(subclass/child class)。類在繼承的同時會繼承父類中的方法。在面向對象中,方法就是子程序。因此,子類Cow、Horse、Sheep能夠直接使用父類Animal中的speak()方法。
因而修改lib/{Cow,Sheep,Horse}.pm
文件。
lib/Cow.pm中:
#!/usr/bin/env perl use strict; use warnings; package Cow; use Animal; # 先裝載Animal our @ISA=qw(Animal); # 繼承Animal sub sound {"moooo"} 1;
lib/Horse.pm中:
#!/usr/bin/env perl use strict; use warnings; package Horse; use Animal; our @ISA=qw(Animal); sub sound { "neigh" } 1;
lib/Sheep.pm中:
#!/usr/bin/env perl use strict; use warnings; package Sheep; use Animal; our @ISA=qw(Animal); sub sound { "baaaah" } 1;
上面的三個模塊文件中,徹底沒有定義speak(),只是使用our @ISA=qw(Animal);
的方式聲明瞭各自的類繼承Animal類。但在speak.pl中能夠直接調用speak()方法:
#!/usr/bin/env perl use strict; use warnings; use lib "lib"; use Cow; use Sheep; use Horse; foreach my $who (qw(Cow Horse Sheep)){ $who->speak(); }
上面父類Animal中,還定義了一個sound(),這是可選的,由於各個子類都定義了屬於本身的sound()。但強烈建議在Animal中也定義好,由於在當前Animal類中的speak()方法中調用了該方法,且Animal全部子類都重寫了sound(),它表明了一種共性。
換個角度,通常是從父類開始寫程序的,sound()做爲具備共性的方法,應該要先定義在父類中。那麼子類中的sound()是重寫父類sound()而來,因此父類的sound()能夠很是抽象,甚至不提供任何功能,僅僅充當一個佔位符。
在此實例中,Animal中的sound()也確實沒有提供任何和叫聲有關的功能,僅僅只是作了一層檢測,當調用到了父類的sound()時,將報錯。這表示子類沒有定義屬於本身的sound(),也就是沒有重寫父類的sound()。對於這種抽象的方法,每一個子類都應該去重寫,定義屬於本身的特性。
繼承的方式有3種:
1.使用base模塊:use base qw(Animal);
2.使用parent模塊:use parent qw(Animal);
3.使用@ISA
數組:
use Animal; our @ISA = qw(Animal);
它們之間並無多大區別,但須要注意的是,@ISA
只是聲明繼承的一種方式,算是比較古老的寫法,parent模塊是在perl v5.10.1才引入的功能,大概是2000年左右,所以若是是此版本以前的perl,須要使用base模塊,或者安裝parent模塊。
base和parent模塊的本質仍是@ISA
。@ISA
表示的是is a
的關係,這是典型的對象與類、子類與父類的關係解釋。
當調用一個方法時,若是在本身的類中找不到,將從@ISA
數組中定義的父類中尋找,能找到則直接調用,不能找到則報錯。例如上面三個子類都沒有定義speak(),當在speak.pl文件中調用這3個模塊中的speak()時,perl將首先搜索各種(或包)中的speak,由於找不到,因此找父類Animal的speak(),能找到,因此成功調用。
子類要實現本身獨有的特性,除了定義父類中沒有的屬性以外,還能夠重寫從父類繼承的方法。例如Cow、Horse、Sheep中的sound()就重寫了父類Animal的sound()。
雖然理論上父類中的speak()能夠重寫,但極可能是沒有必要重寫甚至不該該重寫的,由於重寫可能會帶來破壞,使得能使用父類的地方不能使用子類(參考"里氏替換原則")。
另外,強烈建議儘可能擴展父類的行爲,而不是修改父類的行爲。
例如,新添加一個老鼠子類,它的speak()除了叫一聲外,還多叫一聲。lib/Mouse.pm文件內容以下:
#!/usr/bin/env perl use strict; use warnings; package Mouse; use Animal; our @ISA =qw(Animal); sub sound { "jiji" } sub speak { my $class = shift; print "a $class goes ", $class->sound(), "!\n"; print "jiji\n"; } 1;
如今,在speak.pl中調用Mouse的speak()。
use Mouse; Mouse->speak();
上面執行並無問題。但問題出現了,若是Animal中的goes單詞修改爲了says,那麼Mouse中的goes也得改爲says。這種方式並不合理,因此,必須得保證子類的speak()和父類的speak()能保持以執行,而不能在子類種對任何共性的部分進行硬編碼。
因此,將Mouse的speak()的第一個print,改成調用Animal中的speak()。
#!/usr/bin/env perl use strict; use warnings; package Mouse; use Animal; our @ISA =qw(Animal); sub sound { "jiji" } sub speak { my $class = shift; Animal::speak($class); print "jiji\n"; } 1;
上面經過徹底限定的包名進行speak()的調用,注意傳遞了一個參數$class
給speak(),由於父類Animal中的speak()要求一個參數。
可是問題又再次出現,假如Animal繼承自Dot::Animal
,而Animal自身沒有speak(),上面的寫法就會出錯。稍微好一點的寫法是使用面向對象的調用方式:
sub speak { my $class = shift; $class->Animal::speak(); print "jiji\n"; }
雖然看上去很醜,但確實是能夠正常工做的,它明確指定從Animal類中搜索。但Animal::
是被硬編碼到代碼中的,像硬編碼的行爲能避免則避免。
將上面的代碼再改一改:
sub speak { my $class = shift; $class->SUPER::speak(); print "jiji\n"; }
這樣就解決了硬編碼的問題。SUPER::
表示從@ISA
父類列表中搜索。