本系列:html
第3篇依賴於第2篇,第2篇依賴於1篇。java
如今有父類Animal,子類Horse,它們的代碼分別以下:程序員
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;
lib/Horse.pm中:數組
#!/usr/bin/env perl use strict; use warnings; package Horse; use parent qw(Animal); sub sound { "neigh" } 1;
一個perl程序speak.pl文件:數據結構
#!/usr/bin/env perl use strict; use warnings; use lib "lib"; use Horse; Horse->speak();
執行上面的speak.pl,將輸出:閉包
a Horse goes neigh
上面使用Horse->speak()
的方式調用speak()方法,它首先調用到父類Animal中的speak(由於Horse類中沒有重寫該方法),而後Animal中的speak又從新回調Horse類中的sound()。less
這個speak是全部Horse都共享的,若是想要定義每一個Horse對象都私有的數據呢?好比爲每一個Horse對象命名。這裏Horse的名字就是Horse類的實例數據(在其它編程語言中常稱之爲成員變量),是每一個對象獨有的。編程語言
在Perl的面向對象編程中,一個對象表示的就是一個對內置類型的引用,好比標量引用、數組引用、hash引用。也就是說,所謂對象就是一個指向內置數據結構的引用,這個數據結構能夠認爲是每一個對象私有的成員變量。ide
強烈建議使用hash引用的方式,不過此處先以標量引用的方式開始本文的介紹。
修改speak.pl文件:
#!/usr/bin/env perl use strict; use warnings; use lib "lib"; use Horse; my $name = "baima"; my $bm_horse = \$name; bless $bm_horse,'Horse';
bless的語法爲:
bless REFERENCE,CLASS;
它表示爲CLASS類設置一個惟一標識符,並返回這個惟一標識符,這個惟一標識符是一個數據結構的引用,這個惟一標識符也被稱爲對象。也就是說,對象就是一個引用,因此咱們經常會使用my $obj = bless REF,CLASS;
來返回一個對象。另外一方面,bless表示將一個數據結構和類進行關聯,表示這個數據結構(也許是空的,也許是通過必定初始化的)已經附加在類上,當建立這個類的實例(對象)時,它將返回一個引用,對這個數據結構的引用,換句話說,這個對象就已經擁有了這個數據結構。
以下圖所示:
上面的Class是類,引用變量$obj_ref
是這個類的惟一標識符,它指向一個數據結構,這個數據結構是這個類的屬性。使用Horse進行具體化,Horse是類,$bm_horse
是這個類的惟一標識符,這個引用指向值爲"baima"的標量數據結構,因此$bm_horse
是一個對象,"baima"就是它獨有的屬性。
也就是說,bless $bm_horse,'Horse';
已經建立了一個名爲$bm_horse
的對象,而"baimai"這個屬性是隻屬於這一個對象的數據。
bless生成一個引用後,這個引用是類的實例,能夠經過這個引用變量去調用類的方法。
#!/usr/bin/env perl use strict; use warnings; use lib "lib"; use Horse; my $name = "baima"; my $bm_horse = \$name; bless $bm_horse,'Horse'; print $bm_horse->sound(),"\n";
上面經過對象去調用類方法,它首先搜索出sound()在何處(即類中仍是父類中),而後將參數傳遞給sound()。傳遞的參數列表中,第一個參數是實例的名稱,也就是$bm_horse
,就像經過類名去調用類方法時,傳遞的第一個參數是類名同樣。因此,下面兩個是等價的:
$bm_horse->sound(); Horse::sound($bm_horse);
實際上,bless最初的目的就是經過一個引用來關聯正確的類,以便perl能正確地找到所調用的方法,免去經過硬編碼類名的麻煩。
再調用speak()試試:
$bm_horse->speak();
它將輸出:
a Horse=SCALAR(0xc78610) goes neigh!
這是由於$bm_horse
是一個指向標量數據結構的引用,speak()方法中將其賦值給$class
,$class
也仍然是引用,並且speak()中並無去解除這個引用,因此如此輸出。至於解決方法,留待後文。
由於實例的名稱是每一個對象的惟一標識符,而如今能夠經過傳遞給方法的第一個參數獲取實例的名稱,藉此名稱,能夠進一步地獲取到該實例的其它數據。
如今,在lib/Horse.pm文件中添加一個name方法:
sub name { my $self = shift; $$self; }
注意上面$$self
,由於$self
是對象名,而對象名老是一個引用變量,所以將其解除引用。
而後在speak.pl中調用這個方法:
#!/usr/bin/env perl use strict; use warnings; use lib "lib"; use Horse; my $name = "baima"; my $bm_horse = \$name; bless $bm_horse,'Horse'; print $bm_horse->name()," says ",$bm_horse->sound(),"\n";
該print將輸出:
baima says neigh
perl中幾乎都使用$self
做爲類名或對象名的代名詞,就像java中的"this"同樣。實際上,你可使用任何變量名稱,但約定俗成地,你們都喜歡用self。
前面構造Horse對象是在獨立的speak.pl文件中實現的,這樣生成Horse對象的方式是手動的,是徹底私有的,構造對象時的實例數據(即name屬性)也是徹底暴露的。當在多個文件中都這樣構造Horse對象,早晚會出錯。
因而,在類文件lib/Horse.pm中定義一個構造方法new(),每次要構造Horse對象的時候只需調用這個方法便可。
#!/usr/bin/env perl use strict; use warnings; package Horse; use parent qw(Animal); sub new { my $class = shift; my $name = shift; belss \$name,$class; } sub name { my $self = shift; $$self; } sub sound { "neigh" } 1;
上面在Horse.pm中定義了一個new()方法,該方法裏面包含了bless語句,且做爲new()方法的最後一個語句,表示構造一個對象並返回這個對象的惟一標識符:引用變量表示的對象名。所以,這個new()方法被稱之爲構造方法:用於構造該類的實例。
方法名new()能夠隨意,例如hire(),named()等均可以,但面向對象編程語言中,基本上都使用new這個詞語來表示建立新對象,因此,也建議採用約定俗成的new(),若是使用其它方法名做爲構造方法,請作好註釋。
如今,只要調用Horse中的這個new()方法,就表示在當前包中構建一個Horse的實例(bless的返回值):
my $bm_horse = Horse->new("baima");
注意,bless返回的是對象引用,因此賦值給變量$bm_horse
,這時$bm_horse
將表明這個對象,是這個對象的惟一標識符。
上面調用new()的過程當中,首先找到類方法new(),而後傳遞參數列表('Horse',"baima")
,new()方法中,bless將baima這個數據結構附加到Horse類中,並返回指向該數據結構的引用。之後,經過$bm_horse
就能找到這個數據結構,由於這個數據結構是對象$bm_horse
的實例數據。
在上面lib/Horse.pm中的構造方法new()中是否有Horse所特有的個性內容?徹底沒有。不管是Horse、Cow仍是Sheep的構造方法都是通用的,因此將共性的代碼抽取到父類Animal中。
lib/Animal.pm中:
#!/usr/bin/env perl use strict; use warnings; package Animal; sub new { my $class = shift; my $name = shift; bless \$name,$class; } sub name { my $self = shift; $$self; } sub speak { my $class = shift; print "a $class goes ",$class->sound(),"!\n"; } sub sound { die 'You have to define sound() in a subclass'; } 1;
lib/Horse.pm中:
#!/usr/bin/env perl use strict; use warnings; package Horse; use parent qw(Animal); sub sound { "neigh" } 1;
如此一來,不管是Horse、Cow仍是Sheep都繼承父類Animal中的構造方法new()以及name()。注意,上面name()方法也抽取到了Animal類中,由於它也是共性的,不過本小節暫時用不到該方法,下一小節會修改該方法。
如今,在speak.pl文件中構建一個Horse對象:
my $bm_horse = Horse->new("baima");
而後經過這個對象調用speak方法:
$bm_horse->speak();
它將輸出:
a Horse=SCALAR(0xc78610) goes neigh!
這個實驗前文已經驗證過了。之因此會如此,是由於傳遞給speak()的第一個參數是對象的引用變量,而speak()中並無去解除這個引用。再次看看speak()的代碼:
sub speak { my $class = shift; print "a $class goes ",$class->sound(),"!\n"; }
speak()中的$class期待的實際上是一個類名,而不是對象名,由於類名是具體的字符串,而非引用變量。例如,使用Horse->speak()
就不會出現上面的問題。
如何解決父類中的這種問題,使其能同時處理類名和對象名?
爲了讓父類中的方法能同時處理類名和對象名,能夠加入一個額外的方法對類名和對象名進行判斷。如何判斷是類名仍是對象名?只需使用ref便可,若是ref能返回一個值,表示這是一個引用,說明這是對象,ref返回false,則說明這不是引用,也就是類名。
以前由於name()方法由於共性的緣由被抽取到Animal.pm後並無使用過,這裏派上用場了。
lib/Animal.pm中:
#!/usr/bin/env perl use strict; use warnings; package Animal; sub new { my $class = shift; my $name = shift; bless \$name,$class; } sub name { my $self = shift; ref $self ? $$self : "an unamed Class $self"; # 修改此行 } sub speak { my $class = shift; print $class->name()," goes ",$class->sound(),"!\n"; # 調用name()方法 } sub sound { die 'You have to define sound() in a subclass'; } 1;
這樣speak()就變得共性化,既能處理類名,也能處理對象名。
my $bm_horse = Horse->new("baima"); $bm_horse->speak(); # 傳遞對象名 Horse->speak(); # 傳遞類名
將輸出以下結果:
baima goes neigh! an unamed Class Horse goes neigh!
之因此加入新的方法,是由於在speak()中類名和對象名是相互獨立的,也就是沒法共性的,要麼是類名,要麼是對象名。爲了讓一段代碼共性化,解決方法就是添加額外的代碼將非共性內容化解掉,這些額外的代碼能夠直接加在speak()內部,也能夠放進一個新定義的方法中,而後在speak()中調用這個方法。這是一種編程思想。
常常地,perl使用hash做爲對象的數據結果,這個數據結構中能夠存儲不一樣的數據、引用,甚至是對象,其中hash的key常做爲實例數據(成員變量)。
再次說明,perl面向對象時最經常使用的對象數據結構是hash,但標量、數組也同樣能夠,至少不多用。
想要使用hash數據結構,只需將一個hash結構bless到類上便可。它表示這個hash數據結構附加在類上,bless返回一個引用,這個引用就是對象,因此這個對象指向這個數據結構,從而對象擁有這個數據結構。
例如,綁定一個空的hash結構:
bless {},$class;
上面的bless將一個匿名hash附加到類中。
對於父類Animal來講,因爲已經有了name的屬性,如今若是想要加上一個color屬性,就能夠將這兩個成員屬性放進一個hash結構中:
lib/Animal.pm中:
#!/usr/bin/env perl use strict; use warnings; package Animal; sub new { my $class = shift; my $name = shift; my $self = { Name => $name, Color => $class->default_color(), }; bless $self,$class; } sub name { my $self = shift; ref $self ? $self->{Name} # 此處須要修改,由於$self再也不是標量引用變量,而是hash引用變量 : "an unamed Class $self"; } sub default_color { die "You have to override default_color method in subclasses"; } sub speak { my $class = shift; print $class->name()," goes ",$class->sound(),"!\n"; } sub sound { die 'You have to define sound() in a subclass'; } 1;
上面將一個包含key:Name和Color的hash數據結構bless到類上,其中Name成員變量經過構造對象時傳遞參數賦值,Color則調用各種本身的默認顏色方法default_color(),各個子類必須重寫該方法。這是顯然的,咱們能夠爲某一子類動物設置默認毛色,但不能爲全部動物設置同一種默認毛色。
而後修改lib/Horse.pm和lib/Sheep.pm,重寫default_color():
lib/Horse.pm中:
#!/usr/bin/env perl use strict; use warnings; package Horse; use parent qw(Animal); sub sound { 'neigh' } sub default_color { 'black' } 1;
lib/Sheep.pm中:
#!/usr/bin/env perl use strict; use warnings; package Sheep; use parent qw(Animal); sub sound { 'baaaah' } sub default_color { 'white' } 1;
而後,speak.pl中構造Horse對象和Sheep對象,並訪問本身的成員屬性:
my $bm_horse = Horse->new("baima"); my $by_sheep = Sheep->new("xiaoyang"); print $bm_horse->{Name},"\n"; print $bm_horse->{Color},"\n"; print $by_sheep->{Name},"\n"; print $by_sheep->{Color},"\n";
結果:
baima black xiaoyang white
從父類中繼承構造方法時,建立的對象的數據結構是徹底一致的。若是某個子類想要多添加一些固定的數據元素,可讓子類重寫父類的構造方法。
但須要注意的是,重寫方法時,通常都強烈建議只對父類方法進行擴展,而不該該否認父類方法,徹底修改父類方法(抽象方法除外)。
例如,如今父類Animal中的構造方法以下:
sub new { my $class = shift; my $name = shift; my $self = { Name => $name, Color => $class->default_color(), }; bless $self,$class; }
想要爲子類Horse添加一種固定的屬性,馬的類型是戰馬、比賽用的馬仍是普通的馬。因而,在Horse類中:
package Horse; use parent qw(Animal); sub new { my $self = shift->SUPER::new(@_); $self->{Type} = "Racehorse"; $self; }
注意,上面Horse中的構造方法new()中並無給bless語句。當調用Horse->new()
構建對象的時候,首先調用父類的new(),父類的new會關聯一個hash結構並返回這個hash結構,這個hash結構又賦值給$self
,爲此hash結構添加一種元素後,子類的new()返回$self
,使得這個hash結構成爲子類對象的數據結構。
爲了後面的實驗,本節所修改的Horse內容請刪除。
上面設置Color的時候只能經過方法default_color()設置默認的毛色,但馬有黑馬、棕色馬、條紋馬等等,因此須要能手動設置各類顏色。此外,還要更及時獲取到當前最新的成員變量值,好比獲取某Horse對象的名稱和顏色。這就是俗稱的setter和getter方法的做用。
在此示例中,Name屬性是直接經過構造方法傳值設置的,在邏輯上它惟一標識這個對象(對咱們而言,對perl而言是經過對象引用來惟一識別的),因此Name屬性不該該容許從新設置。再者,由於設置和獲取各對象的屬性的代碼是共性的,因此直接將這兩類方法寫到父類Animal中。
lib/Animal.pm中新加的代碼片斷:
sub set_color { my $self = shift; $self->{Color} = shift; } sub get_color { my $self = shift; $self->{Color}; } sub get_name { shift->{Name}; }
如今能夠爲每一個Horse或Sheep對象都設置對象本身的顏色,而且能獲取顏色和名稱:
my $bm_horse = Horse->new("baima"); my $by_sheep = Sheep->new("xiaoyang"); $bm_horse->set_color("white-and-black"); print $bm_horse->get_color(),"\n"; print $by_sheep->get_name(),"\n";
結果以下:
white-and-black xiaoyang
注意上面get_name()中的一種簡寫方式:shift->{NAME}
,shift沒有給參數,因此它的操做對象是@_
,它等價於(shift @_)->{Name}
,也等價於:
my $self = shift; $self->{Name};
在爲setter方法進行編碼的時候,須要考慮它的返回值,通常來講有如下4種返回值類型:
這4種返回值各有優缺點,但不管如何都請註釋好返回值的類型,而且設計好以後就別再修改。
第(1)種是最通用、最多見也最簡單的行爲,傳遞什麼參數給setter,就返回什麼參數值,正如set_color()同樣:
sub set_color { my $self = shift; $self->{Color} = shift; }
通常來講,這種setter方法是放在空上下文(void context)中執行的,但在perl中也能夠直接輸出它:print set_color("COLOR")
。
第(2)種要返回設置以前的值,也很簡單,只需使用一個臨時變量存儲一下原始值並返回該變量便可:
sub set_color { my $self = shift; my $temp = $self->{Color}; $self->{Color} = shift; $temp; }
這裏有一點點小優化。由於是set,因此它多是在空上下文中執行的,也就是說這時返回以前的值是多餘的。能夠經過wantarray
來判斷一下,wantarray函數用於檢查執行上下文,若是在列表上下文中則返回true,標量上下文中則返回false,空上下文中則返回undef。
sub set_color { my $self = shift; if(defined wantarray){ # 非空上下文,返回值有用 my $temp = $self->{Color}; $self->{Color} = shift; $temp; } else { # 空上下文,無需返回值 $self->{Color} = shift; } }
第(3)種返回對象自身:
sub set_color { my $self = shift; $self->{Color} = shift; $self; }
通常來講不會用到這種狀況。但有時候有奇效,例如能夠造成對象鏈。例如,Person有4個成員變量:Name,Age,Height,Weight,它們的setter方法都返回對象自身,那麼能夠:
my $people = Person->set_name("abc")->set_age(23)->set_height(168)->set_weigth(60); # 格式化一下: my $people = Person->set_name("abc") ->set_age(23) ->set_height(168) ->set_weigth(60);
第(4)種返回布爾值有時候很是有效,特別是對於常常更新出錯的狀況。若是是前3種返回值方式,會拋出異常,須要判斷並使用die進行終止。
在面向對象編程中,常使用一個術語don't look inside box
來表示不要暴露對象的成員數據。
經過$obj_ref->{KEY}
的方式能夠在類的外部訪問或設置類的數據結構(成員變量),這是違反對象封裝原則的,它將每一個對象的內部屬性都暴露出來了。對象就像是一個黑盒子,$obj_ref->{KEY}
就像是將鎖鏈撬開同樣。
面向對象的目的之一是讓Animal或Horse的維護者能夠對它們的方法能獨立地作出合理的修改,而且修改後那些已經導出的接口仍然可以正常工做。爲何直接訪問hash結構違反了這個原則?當Animal的Color屬性再也不使用顏色的名稱做爲它的值時,而是使用RGB三原色的方式來存儲顏色呢?
在此示例中,以一個虛構的模塊Color::Conversions
來修改顏色數據的格式,該模塊有兩個函數rgb_to_name()和name_to_rgb(),用於轉換RGB和顏色的字符串名稱,其中name_to_rgb()返回的是一個包含RGB三原色的數組引用。
能夠修改set_color()和get_color()方法:
use Color::Conversions qw(rgb_to_name name_to_rgb); sub set_color { my $self = shift; my $color_name = shift; $self->{Color} = name_to_rgb($color_name); } sub get_color { my $self = shift; rgb_to_name($self->{Color}); }
如今咱們能夠照舊使用setter和getter,但內部其實已經改變了,這些改變對使用者來講是透明的。此外,咱們還能夠添加額外的接口,使得咱們能夠直接設置RGB格式的顏色:
sub set_color_rgb { my $self = shift; $self->{Color} = [@_]; } sub get_color_rgb { my $self = shift; @{ $self->{Color} }; }
若是咱們在類的外面直接使用$bm_horse->{Color}
,將沒法直接查看,由於它是一個RGB三原色元素列表的引用,而非直接顯示出來的RGB元素值或顏色名稱。
這正是面向對象編程所鼓勵的行爲,對於perl而言,只需將成員變量對應的值設置爲一個引用便可。以關聯hash數據結構的Animal類爲例:
Animal |--------------------------| |-> KEY1 => $ref_value1 | |-> KEY2 => $ref_value2 | |-> KEY2 => $ref_value2 | |--------------------------|
爲了讓數據經過引用的方式隱藏起來,且能經過getter方法查找出來,須要合理設計setter和getter方法。例如,讓setter以普通的字符串爲參數,但卻將其存儲到一個引用中,讓getter以引用爲參數,但卻返回人眼可識別的內容。
對於面向對象來講,這兩個方法寫的實在太頻繁了。perl一切從簡的原則,天然也要將其簡化書寫:
sub get_color { $_[0]->{Color} } sub set_color { $_[0]->{Color} = $_[1] }
或者:
sub get_color { shift->{Color} } sub set_color { pop->{Color} = pop }
若是不考慮默認傳遞的類名或對象名參數,getter方法一般是不含參數的,setter方法一般是包含參數的。經過這個特性,能夠將getter和setter合併起來:
sub get_set_color { my $self = shift; if(@_) { # 有參數,說明是setter $self->{Color} = shift; } else { # 沒有參數,說明是getter $self->{Color}; } }
或者簡寫的:
sub get_set_color { @_ > 1 ? pop->{Color} = pop : shift->{Color} }
使用時:
# 設置顏色 $bm_horse->get_set_color("blue"); # 獲取顏色 print $bm_hore->get_set_color(),"\n";
在perl中全部的方法都是子程序,沒有額外的功能來區分一個方法是類方法仍是實例方法。對咱們來講,若是傳遞的第一個參數爲類名的是類方法,若是傳遞的第一個參數爲對象引用名的方法是實例方法。
好在perl提供了ref函數,能夠經過檢查引用的方式來檢查這是類方法仍是實例方法。
use Carp qw(croak); sub instance_method { ref(my $self = shift) or croak "this is a instance method"; ...CODE... } sub class_method { ref(my $class = shift) and croak "this is a Class method"; ...CODE... }
這裏使用croak替換了die,這樣報錯的時候能夠直接告訴錯誤所在的行數。
私有方法是指類中不該該被外界訪問的方法,它能夠在類自身其它地方調用,但不該該被對象或其它外界訪問以避免破壞數據。
例如,類Class1中的方法get_total()
內部調用一個私有方法_get_nums()
,經過該私有方法返回的數組來獲取一個包含數值的數組。若是有一個子類Class2繼承了Class1,且重寫了_get_nums()
使其返回一個數組引用而非數組,這時對象要調用的get_total()
整段代碼就廢了。
package Class1; sub new { my ($class,$args) = @_; return bless $args,$class; } sub get_total { my $self = shift; my @nums = $self->_pri_sub; # 期待該私有方法返回一個列表 my $total = 0; foreach (@nums) { $total += $_; } return $total; } sub _pri_sub { my $self = shift; ...some codes... return @nums; # 返回一個數組 } 1;
通常來講,這樣的問題並不常發生,由於程序畢竟是程序員寫的,遵循規範的狀況下,你們都知道這是什麼意思。但若是程序比較龐大,也許無心中就重寫了一個私有方法。面向對象,一個最基本的規則就是保護數據不被泄漏、不被破壞。
但perl中全部的方法都是public(公共的),誰都能訪問,並無提供讓方法私有化的功能。只是以一種呼籲式的規範,讓你們約定俗成地使用下劃線"_"做爲方法名的前綴來表示這是一個私有方法(例如sub _name {}
)。但這只是一種無聲的聲明"這是私有方法,外界請別訪問",perl並不限制咱們從外界去訪問下劃線開頭的方法。
要實現方法的真正私有化,能夠將匿名子程序賦值給一個變量來實現,或者經過閉包的方式實現。
sub get_total { my $self = shift; my @nums = $self->$_pri_sub(ARGS); my $total = 0; foreach (@nums) { $total += $_; } return $total; } my $_pri_sub = sub { my $self = shift; ...some codes... return @nums; }
須要注意的是,上面$self->$_pri_sub()
中箭頭的右邊是一個變量,在其它面嚮對象語言中是沒法將變量做爲方法名的,但perl支持。
由於$_pri_sub
是詞法變量,構造的對象沒法取得這樣的數據。但經過一些高級技術,對象仍是可以取得這個方法,要想徹底私有化,經過閉包實現:
sub get_total { my $self = shift; my $_pri_sub = sub { my $self = shift; ...some codes... return @nums; } my @nums = $self->$_pri_sub(ARGS); my $total = 0; foreach (@nums) { $total += $_; } return $total; }
UNIVERSAL是一切類的祖先,全部的類都繼承於它。它提供了3個方法:isa()、cat()和VERSION(),在v5.10.1和以後,還提供了另外一個方法DOES()。
1.isa()用於判斷某個給定的對象(或類)與某個類是否知足is a
的關係,也就是"對象是不是某個類的實例,類1是不是類2的子類"。
$object_or_class->isa(CLASS);
2.can()用於判斷某個給定的對象(或類)是否可以使用某方法。
$object_or_class->can($method_name);
需注意,can()方法檢測結果爲真的時候,其返回值是該方法的引用。也就是說,能夠直接將can()賦值給一個方法的引用變量,避免屢次書寫。如下是等價的寫法:
if(my $method = $obj->can($method_name)){ $obj->$method; } if($obj->can($method_name)){ $obj->$methdo_name; }
3.VERSION()用於返回對象(或類)的版本號。
設置了our $VERSION=...;
以後,既能夠經過繼承自UNIVERSAL的VERSION()方法獲取版本號,也能夠經過對象獲取VERSION變量值:
$obj->VERSION(); $obj->VERSION;
Perl支持多重繼承。假設Class3多重繼承Class1和Class2:
package Class3; # 1. use base qw(Class1 Class2); # 2. use parent qw(Class1 Class2); # 3. use Class1; use Class2; our @ISA = qw(Class1 Class2);
但不管是哪一種語言,都強烈建議不要使用多重繼承。
假設Class1和Class2都直接繼承自UNIVERSAL類,如今Class3多重繼承Class1和Class2。
UNIVERSAL | | Class1 Class2 | | Class3
假設Class2有方法eat(),Class1沒有,且Class3沒有寫eat(),那麼Class3的實例調用eat()方法的時候,會調用到Class2的eat()嗎?
默認狀況下,Perl搜索方法的規則是從左搜索,深度優先。意味着use base qw(Class1 Class2)
時,當Class3自身找不到eat()時,將先搜索左邊的Class1,搜索完沒發現eat(),將搜索Class1的父類UNIVERSAL。也就是說永遠也不會去搜索Class2。
實際上,搜索父類時是搜索@ISA
中的元素,因此是從左開始搜索。
可是可使用CPAN上的C3或者mro模塊,它們實現從左搜索,廣度優先的搜索規則。也就是說,對於use base qw(Class1 Class2)
,當Class3自身找不到eat()時,將先找左邊的Class1的eat(),找不到再找右邊的Class2的eat(),還找不到的話最後找父類的eat()。