Perl 面向?qū)ο?/h2>

Perl 中有兩種不同地面向?qū)ο缶幊痰膶?shí)現(xiàn):

  • 一是基于匿名哈希表的方式,每個(gè)對象實(shí)例的實(shí)質(zhì)就是一個(gè)指向匿名哈希表的引用。在這個(gè)匿名哈希表中,存儲(chǔ)來所有的實(shí)例屬性。
  • 二是基于數(shù)組的方式,在定義一個(gè)類的時(shí)候,我們將為每一個(gè)實(shí)例屬性創(chuàng)建一個(gè)數(shù)組,而每一個(gè)對象實(shí)例的實(shí)質(zhì)就是一個(gè)指向這些數(shù)組中某一行索引的引用。在這些數(shù)組中,存儲(chǔ)著所有的實(shí)例屬性。

面向?qū)ο蠡A(chǔ)概念

面向?qū)ο笥泻芏嗷A(chǔ)概念,這里我們接收三個(gè):對象、類和方法。

  • 對象:對象是對類中數(shù)據(jù)項(xiàng)的引用。.

  • :類是個(gè)Perl包,其中含提供對象方法的類。

  • 方法:方法是個(gè)Perl子程序,類名是其第一個(gè)參數(shù)。

Perl 提供了 bless() 函數(shù),bless 是用來構(gòu)造對象的, 通過 bless 把一個(gè)引用和這個(gè)類名相關(guān)聯(lián),返回這個(gè)引用就構(gòu)造出一個(gè)對象。


類的定義

一個(gè)類只是一個(gè)簡單的包。

可以把一個(gè)包當(dāng)作一個(gè)類用,并且把包里的函數(shù)當(dāng)作類的方法來用。

Perl 的包提供了獨(dú)立的命名空間,所以不同包的方法與變量名不會(huì)沖突。

Perl 類的文件后綴為 .pm。

接下來我們創(chuàng)建一個(gè) Person 類:

package Person;

類的代碼范圍到腳本文件的最后一行,或者到下一個(gè) package 關(guān)鍵字前。


創(chuàng)建和使用對象

創(chuàng)建一個(gè)類的實(shí)例 (對象) 我們需要定義一個(gè)構(gòu)造函數(shù),大多數(shù)程序使用類名作為構(gòu)造函數(shù),Perl 中可以使用任何名字。

你可以使用多種 Perl 的變量作為 Perl 的對象。大多數(shù)情況下我們會(huì)使用引用數(shù)組或哈希。

接下來我們?yōu)?Person 類創(chuàng)建一個(gè)構(gòu)造函數(shù),使用了 Perl 的哈希引用。

在創(chuàng)建對象時(shí),你需要提供一個(gè)構(gòu)造函數(shù),它是一個(gè)子程序,返回對象的引用。

實(shí)例如下:

package Person;
sub new
{
    my $class = shift;
    my $self = {
        _firstName => shift,
        _lastName  => shift,
        _ssn       => shift,
    };
    # 輸出用戶信息
    print "名字:$self->{_firstName}\n";
    print "姓氏:$self->{_lastName}\n";
    print "編號(hào):$self->{_ssn}\n";
    bless $self, $class;
    return $self;
}

接下來我們創(chuàng)建一個(gè)對象:

$object = new Person( "小明", "王", 23234345);

定義方法

Perl類的方法只但是是個(gè)Perl子程序而已,也即通常所說的成員函數(shù)。

Perl面向?qū)ο笾蠵erl的方法定義不提供任何特別語法,但規(guī)定方法的第一個(gè)參數(shù)為對象或其被引用的包。

Perl 沒有提供私有變量,但我們可以通過輔助的方式來管理對象數(shù)據(jù)。

接下來我們定義一個(gè)獲取名字的方法:

sub getFirstName {
    return $self->{_firstName};
}

同樣也可以這么寫:

sub setFirstName {
    my ( $self, $firstName ) = @_;
    $self->{_firstName} = $firstName if defined($firstName);
    return $self->{_firstName};
}

接下來我們修改 Person.pm 文件的代碼,如下所示:

#!/usr/bin/perl 

package Person;

sub new
{
    my $class = shift;
    my $self = {
        _firstName => shift,
        _lastName  => shift,
        _ssn       => shift,
    };
    # 輸出用戶信息
    print "名字:$self->{_firstName}\n";
    print "姓氏:$self->{_lastName}\n";
    print "編號(hào):$self->{_ssn}\n";
    bless $self, $class;
    return $self;
}
sub setFirstName {
    my ( $self, $firstName ) = @_;
    $self->{_firstName} = $firstName if defined($firstName);
    return $self->{_firstName};
}

sub getFirstName {
    my( $self ) = @_;
    return $self->{_firstName};
}
1;

employee.pl 腳本代碼如下:

#!/usr/bin/perl

use Person;

$object = new Person( "小明", "王", 23234345);
# 獲取姓名
$firstName = $object->getFirstName();

print "設(shè)置前姓名為 : $firstName\n";

# 使用輔助函數(shù)設(shè)置姓名
$object->setFirstName( "小強(qiáng)" );

# 通過輔助函數(shù)獲取姓名
$firstName = $object->getFirstName();
print "設(shè)置后姓名為 : $firstName\n";

執(zhí)行以上程序后,輸出結(jié)果為:

$ perl employee.pl
名字:小明
姓氏:王
編號(hào):23234345
設(shè)置前姓名為 : 小明
設(shè)置后姓名為 : 小強(qiáng)

繼承

Perl 里 類方法通過@ISA數(shù)組繼承,這個(gè)數(shù)組里面包含其他包(類)的名字,變量的繼承必須明確設(shè)定。

多繼承就是這個(gè)@ISA數(shù)組包含多個(gè)類(包)名字。

通過@ISA只能繼承方法,不能繼承數(shù)據(jù)。

接下來我們創(chuàng)建一個(gè) Employee 類繼承 Person 類。

Employee.pm 文件代碼如下所示:

#!/usr/bin/perl

package Employee;
use Person;
use strict;
our @ISA = qw(Person);    # 從 Person 繼承

現(xiàn)在 Employee 類包含了 Person 類的所有方法和屬性,我們在 main.pl 文件中輸入以下代碼,并執(zhí)行:

#!/usr/bin/perl

use Employee;

$object = new Employee( "小明", "王", 23234345);
# 獲取姓名
$firstName = $object->getFirstName();

print "設(shè)置前姓名為 : $firstName\n";

# 使用輔助函數(shù)設(shè)置姓名
$object->setFirstName( "小強(qiáng)" );

# 通過輔助函數(shù)獲取姓名
$firstName = $object->getFirstName();
print "設(shè)置后姓名為 : $firstName\n";

執(zhí)行以上程序后,輸出結(jié)果為:

$ perl main.pl
名字:小明
姓氏:王
編號(hào):23234345
設(shè)置前姓名為 : 小明
設(shè)置后姓名為 : 小強(qiáng)

方法重寫

上面實(shí)例中,Employee 類繼承了 Person 類,但如果 Person 類的方法無法滿足需求,就需要對其方法進(jìn)行重寫。

接下來我們在 Employee 類中添加一些新方法,并重寫了 Person 類的方法:

#!/usr/bin/perl

package Employee;
use Person;
use strict;
our @ISA = qw(Person);    # 從 Person 繼承

# 重寫構(gòu)造函數(shù)
sub new {
    my ($class) = @_;

    # 調(diào)用父類的構(gòu)造函數(shù)
    my $self = $class->SUPER::new( $_[1], $_[2], $_[3] );
    # 添加更多屬性
    $self->{_id}   = undef;
    $self->{_title} = undef;
    bless $self, $class;
    return $self;
}

# 重寫方法
sub getFirstName {
    my( $self ) = @_;
    # 這是子類函數(shù)
    print "這是子類函數(shù)\n";
    return $self->{_firstName};
}

# 添加方法
sub setLastName{
    my ( $self, $lastName ) = @_;
    $self->{_lastName} = $lastName if defined($lastName);
    return $self->{_lastName};
}

sub getLastName {
    my( $self ) = @_;
    return $self->{_lastName};
}

1;

我們在 main.pl 文件中輸入以下代碼,并執(zhí)行:

#!/usr/bin/perl

use Employee;

$object = new Employee( "小明", "王", 23234345);
# 獲取姓名,使用修改后的構(gòu)造函數(shù)
$firstName = $object->getFirstName();

print "設(shè)置前姓名為 : $firstName\n";

# 使用輔助函數(shù)設(shè)置姓名
$object->setFirstName( "小強(qiáng)" );

# 通過輔助函數(shù)獲取姓名
$firstName = $object->getFirstName();
print "設(shè)置后姓名為 : $firstName\n";

執(zhí)行以上程序后,輸出結(jié)果為:

$ perl main.pl
名字:小明
姓氏:王
編號(hào):23234345
這是子類函數(shù)
設(shè)置前姓名為 : 小明
這是子類函數(shù)
設(shè)置后姓名為 : 小強(qiáng)

默認(rèn)載入

如果在當(dāng)前類、當(dāng)前類所有的基類、還有 UNIVERSAL 類中都找不到請求的方法, 這時(shí)會(huì)再次查找名為 AUTOLOAD() 的一個(gè)方法。如果找到了 AUTOLOAD,那么就會(huì) 調(diào)用,同時(shí)設(shè)定全局變量 $AUTOLOAD 的值為缺失的方法的全限定名稱。

如果還不行,那么 Perl 就宣告失敗并出錯(cuò)。

如果你不想繼承基類的 AUTOLOAD,很簡單,只需要一句:

sub AUTOLOAD;

析構(gòu)函數(shù)及垃圾回收

當(dāng)對象的最后一個(gè)引用釋放時(shí),對象會(huì)自動(dòng)析構(gòu)。

如果你想在析構(gòu)的時(shí)候做些什么,那么你可以在類中定義一個(gè)名為"DESTROY"的方法。它將在適合的時(shí)機(jī)自動(dòng)調(diào)用,并且按照你的意思執(zhí)行額外的清理動(dòng)作。

package MyClass;
...
sub DESTROY
{
    print "MyClass::DESTROY called\n";
}

Perl 會(huì)把對象的引用作為 唯一的參數(shù)傳遞給 DESTROY。注意這個(gè)引用是只讀的,也就是說你不能通過訪問$_[0] 來修改它。(譯者注:參見 perlsub)但是對象自身(比如 "${$_[0]" 或者 "@{$_[0]}" 還有 "%{$_[0]}" 等等)還是可寫的。

如果你在析構(gòu)器返回之前重新 bless 了對象引用,那么 Perl 會(huì)在析構(gòu)器返回之后接著調(diào)用你重新 bless 的那個(gè)對象的 DESTROY 方法。這可以讓你有機(jī)會(huì)調(diào)用基類或者你指定的其它類的析構(gòu)器。需要說明的是,DESTROY 也可以手工調(diào)用,但是通常沒有必要這么做。

在當(dāng)前對象釋放后,包含在當(dāng)前對象中的其它對象會(huì)自動(dòng)釋放。


Perl 面向?qū)ο髮?shí)例

我們可以通過以下實(shí)例來進(jìn)一步理解Perl面向?qū)ο蟮膽?yīng)用:

#!/usr/bin/perl

# 下面是簡單的類實(shí)現(xiàn)
package MyClass;

sub new
{
   print "MyClass::new called\n";
   my $type = shift;            # 包名
   my $self = {};               # 引用空哈希
   return bless $self, $type;   
}

sub DESTROY
{
   print "MyClass::DESTROY called\n";
}

sub MyMethod
{
   print "MyClass::MyMethod called!\n";
}


# 繼承實(shí)現(xiàn)
package MySubClass;

@ISA = qw( MyClass );

sub new
{
   print "MySubClass::new called\n";
   my $type = shift;            # 包名
   my $self = MyClass->new;     # 引用空哈希
   return bless $self, $type;  
}

sub DESTROY
{
   print "MySubClass::DESTROY called\n";
}

sub MyMethod
{
   my $self = shift;
   $self->SUPER::MyMethod();
   print "   MySubClass::MyMethod called!\n";
}

# 調(diào)用以上類的主程序
package main;

print "調(diào)用 MyClass 方法\n";

$myObject = MyClass->new();
$myObject->MyMethod();

print "調(diào)用 MySubClass 方法\n";

$myObject2 = MySubClass->new();
$myObject2->MyMethod();

print "創(chuàng)建一個(gè)作用域?qū)ο骪n";
{
  my $myObject2 = MyClass->new();
}
# 自動(dòng)調(diào)用析構(gòu)函數(shù)

print "創(chuàng)建對象\n";
$myObject3 = MyClass->new();
undef $myObject3;

print "腳本執(zhí)行結(jié)束...\n";
# 自動(dòng)執(zhí)行析構(gòu)函數(shù)

執(zhí)行以上程序,輸出結(jié)果為:

調(diào)用 MyClass 方法
MyClass::new called
MyClass::MyMethod called!
調(diào)用 MySubClass 方法
MySubClass::new called
MyClass::new called
MyClass::MyMethod called!
   MySubClass::MyMethod called!
創(chuàng)建一個(gè)作用域?qū)ο?MyClass::new called
MyClass::DESTROY called
創(chuàng)建對象
MyClass::new called
MyClass::DESTROY called
腳本執(zhí)行結(jié)束...
MyClass::DESTROY called
MySubClass::DESTROY called