Perl面向對象(2):對象

本系列: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類的實例數據(在其它編程語言中常稱之爲成員變量),是每一個對象獨有的。編程語言

bless建立實例數據:對象

在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()中調用這個方法。這是一種編程思想。

使用hash數據結構:添加額外的成員變量

常常地,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內容請刪除。

設置和獲取實例數據:setter & getter

上面設置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返回值的問題

在爲setter方法進行編碼的時候,須要考慮它的返回值,通常來講有如下4種返回值類型:

  • (1).set成功後的值
  • (2).set以前的值
  • (3).返回對象自身
  • (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以引用爲參數,但卻返回人眼可識別的內容。

簡化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方法一般是包含參數的。經過這個特性,能夠將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,這樣報錯的時候能夠直接告訴錯誤所在的行數。

私有方法(private method)

私有方法是指類中不該該被外界訪問的方法,它能夠在類自身其它地方調用,但不該該被對象或其它外界訪問以避免破壞數據。

例如,類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

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()。

相關文章
相關標籤/搜索