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