Perl 面向物件

Perl 面向物件

Perl 中有兩種不同地面向物件程式設計的實現:

  • 一是基於匿名雜湊表的方式,每個物件例項的實質就是一個指向匿名雜湊表的引用。在這個匿名雜湊表中,儲存了所有的例項屬性。

  • 二是基於陣列的方式,在定義一個類的時候,我們將為每一個例項屬性建立一個數組,而每一個物件例項的實質就是一個指向這些陣列中某一行索引的引用。在這些陣列中,儲存著所有的例項屬性。


面向物件基礎概念

面向物件有很多基礎概念,這裡我們接收三個:物件、類和方法。

  • 物件:物件是對類中資料項的引用。.

  • :類是個Perl包,其中含提供物件方法的類。

  • 方法:方法是個Perl子程式,類名是其第一個引數。

Perl 提供了 bless() 函式,bless 是用來構造物件的, 通過 bless 把一個引用和這個類名相關聯,返回這個引用就構造出一個物件。


類的定義

一個類只是一個簡單的包。

可以把一個包當作一個類用,並且把包裡的函式當作類的方法來用。

Perl 的包提供了獨立的名稱空間,所以不同包的方法與變數名不會衝突。

Perl 類的檔案字尾為 .pm。

接下來我們建立一個 Person 類:

package Person;

類的程式碼範圍到指令碼檔案的最後一行,或者到下一個 package 關鍵字前。


建立和使用物件

建立一個類的例項 (物件) 我們需要定義一個建構函式,大多數程式使用類名作為建構函式,Perl 中可以使用任何名字。

你可以使用多種 Perl 的變數作為 Perl 的物件。大多數情況下我們會使用引用陣列或雜湊。

接下來我們為 Person 類建立一個建構函式,使用了 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 "編號:$self->{_ssn}\n"; bless $self, $class; return $self; }

接下來我們建立一個物件:

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

定義方法

Perl類的方法只但是是個Perl子程式而已,也即通常所說的成員函式。

Perl面向物件中Perl的方法定義不提供任何特別語法,但規定方法的第一個引數為物件或其被引用的包。

Perl 沒有提供私有變數,但我們可以通過輔助的方式來管理物件資料。

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

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 "編號:$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 "設定前姓名為 : $firstName\n"; # 使用輔助函式設定姓名 $object->setFirstName( "小強" ); # 通過輔助函式獲取姓名 $firstName = $object->getFirstName(); print "設定後姓名為 : $firstName\n";

執行以上程式後,輸出結果為:

$ perl employee.pl
名字:小明
姓氏:王
編號:23234345
設定前姓名為 : 小明
設定後姓名為 : 小強

繼承

Perl 裡 類方法通過@ISA陣列繼承,這個數組裡麵包含其他包(類)的名字,變數的繼承必須明確設定。

多繼承就是這個@ISA陣列包含多個類(包)名字。

通過@ISA只能繼承方法,不能繼承資料。

接下來我們建立一個 Employee 類繼承 Person 類。

Employee.pm 檔案程式碼如下所示:

例項

#!/usr/bin/perl package Employee; use Person; use strict; our @ISA = qw(Person); # 從 Person 繼承

現在 Employee 類包含了 Person 類的所有方法和屬性,我們在 main.pl 檔案中輸入以下程式碼,並執行:

例項

#!/usr/bin/perl use Employee; $object = new Employee( "小明", "", 23234345); # 獲取姓名 $firstName = $object->getFirstName(); print "設定前姓名為 : $firstName\n"; # 使用輔助函式設定姓名 $object->setFirstName( "小強" ); # 通過輔助函式獲取姓名 $firstName = $object->getFirstName(); print "設定後姓名為 : $firstName\n";

執行以上程式後,輸出結果為:

$ perl main.pl
名字:小明
姓氏:王
編號:23234345
設定前姓名為 : 小明
設定後姓名為 : 小強

方法重寫

上面例項中,Employee 類繼承了 Person 類,但如果 Person 類的方法無法滿足需求,就需要對其方法進行重寫。

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

例項

#!/usr/bin/perl package Employee; use Person; use strict; our @ISA = qw(Person); # 從 Person 繼承 # 重寫建構函式 sub new { my ($class) = @_; # 呼叫父類的建構函式 my $self = $class->SUPER::new( $_[1], $_[2], $_[3] ); # 新增更多屬性 $self->{_id} = undef; $self->{_title} = undef; bless $self, $class; return $self; } # 重寫方法 sub getFirstName { my( $self ) = @_; # 這是子類函式 print "這是子類函式\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 檔案中輸入以下程式碼,並執行:

例項

#!/usr/bin/perl use Employee; $object = new Employee( "小明", "", 23234345); # 獲取姓名,使用修改後的建構函式 $firstName = $object->getFirstName(); print "設定前姓名為 : $firstName\n"; # 使用輔助函式設定姓名 $object->setFirstName( "小強" ); # 通過輔助函式獲取姓名 $firstName = $object->getFirstName(); print "設定後姓名為 : $firstName\n";

執行以上程式後,輸出結果為:

$ perl main.pl
名字:小明
姓氏:王
編號:23234345
這是子類函式
設定前姓名為 : 小明
這是子類函式
設定後姓名為 : 小強

預設載入

如果在當前類、當前類所有的基類、還有 UNIVERSAL 類中都找不到請求的方法, 這時會再次查詢名為 AUTOLOAD() 的一個方法。如果找到了 AUTOLOAD,那麼就會 呼叫,同時設定全域性變數 $AUTOLOAD 的值為缺失的方法的全限定名稱。

如果還不行,那麼 Perl 就宣告失敗並出錯。

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

sub AUTOLOAD;

解構函式及垃圾回收

當物件的最後一個引用釋放時,物件會自動析構。

如果你想在析構的時候做些什麼,那麼你可以在類中定義一個名為"DESTROY"的方法。它將在適合的時機自動呼叫,並且按照你的意思執行額外的清理動作。

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

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

如果你在析構器返回之前重新 bless 了物件引用,那麼 Perl 會在析構器返回之後接著呼叫你重新 bless 的那個物件的 DESTROY 方法。這可以讓你有機會呼叫基類或者你指定的其它類的析構器。需要說明的是,DESTROY 也可以手工呼叫,但是通常沒有必要這麼做。

在當前物件釋放後,包含在當前物件中的其它物件會自動釋放。


Perl 面向物件例項

我們可以通過以下例項來進一步理解Perl面向物件的應用:

例項

#!/usr/bin/perl # 下面是簡單的類實現 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"; } # 繼承實現 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"; } # 呼叫以上類的主程式 package main; print "呼叫 MyClass 方法\n"; $myObject = MyClass->new(); $myObject->MyMethod(); print "呼叫 MySubClass 方法\n"; $myObject2 = MySubClass->new(); $myObject2->MyMethod(); print "建立一個作用域物件\n"; { my $myObject2 = MyClass->new(); } # 自動呼叫解構函式 print "建立物件\n"; $myObject3 = MyClass->new(); undef $myObject3; print "指令碼執行結束...\n"; # 自動執行解構函式

執行以上程式,輸出結果為:

呼叫 MyClass 方法
MyClass::new called
MyClass::MyMethod called!
呼叫 MySubClass 方法
MySubClass::new called
MyClass::new called
MyClass::MyMethod called!
   MySubClass::MyMethod called!
建立一個作用域物件
MyClass::new called
MyClass::DESTROY called
建立物件
MyClass::new called
MyClass::DESTROY called
指令碼執行結束...
MyClass::DESTROY called
MySubClass::DESTROY called