perl本身具有參照這類的物件,但是參照適用簡單類型的物件,例如統一格式、規律變化的變數。若需要複雜一點,類似類別這種類型的物件時,就得借助 bless 函式的幫助。
簡單來說,bless幫助大家建立perl的類別。
一般程式的類別
現代的程式語言多半都封裝在類別中,不像perl是沒有類別的語言,一般程式語一般程式的類別大概是長這個樣子:
var variable1;
function dosomething(){
...
}
}
類別裡面有變數 variable1和方法 dosomething(),我們藉由NEW實體化後就能叫用這個類別:
obj->dosomething();
print obj->variable1;
這就是類別方便的地方,當然類別不會這麼簡單,今天同樣的事希望perl這古老的語言也做得到。
PERL的類別寫法
perl可以藉由包裹package來完成類別,package一般我們會寫成獨立的檔案,再用 use來引入。這部分可以參考[PERL] 18-套件及模組@新精讚
這次的範例我寫一個數學小函式Math,他的功能只是算代入的兩個數的算數和幾何平均數。
Math.pm
package Math; my $a; my $b; sub setAB { $a=shift; $b=shift; } sub ari{ return ($a+$b)/2; } sub geo { return sqrt($a*$b); } 1;
上面的類別Math很簡單,只有三個函式 setAB、ari、geo。
接下來使用 01.pl 這個程式去執行
01.pl
#!/usr/bin/perl -w use Cwd qw(abs_path); use lib abs_path; use Math; &Math::setAB(3,4); print &Math::ari; print "\n"; print &Math::geo; print "\n";
第2行和第3行 為了讓自寫的Math.pm和 01.pl 放在一起能被呼叫到,採用[4]的作法把@INC引入目前的目錄。
第4行 使用Math包裹。
執行結果
3.5
3.46410161513775
上面的寫法完全沒有問題,也運作的很好,但嚴格說來並不是很好的方式,因為他採用的是靜態函式的方式叫用,對於複雜一點的程式會有變數混雜的情況。
引入BLESS函式
接下來說今天得重頭戲,bless函式,這是perl內建的函式,不必use其餘套件。
BLESS函式有三種寫法
1
bless $ref, $package;
2
bless $ref, "";
3
bless $ref;
$ref 稱為 referred-to item,中文不知怎麼翻,就叫他被參考物件吧,一般都是放入hash變數。
以上三種以第1種最常見,也是推薦用法;第2種是建立main的參考物件;第3種是建立目前所在位置的參考物件,相當於this。
以上程式改成 bless函式的寫法,改成如下∶
02.pl
#!/usr/bin/perl -w use Cwd qw(abs_path); use lib abs_path; use Math; my $obj={}; bless($obj, "Math"); $obj->setAB(3,4); print $obj->ari; print "\n"; print $obj->geo; print "\n";
第6行和第7行,簡單理解就是將Math這個類別指給 $obj 當參照。
後面就使用物件的方式呼叫,感覺是不是先進多了?
引用的物件 Math 也得進行修改:
Math.pm (修正)
package Math; my $a; my $b; sub setAB { $class=shift; $a=shift; $b=shift; } sub ari{ return ($a+$b)/2; } sub geo { return sqrt($a*$b); } 1;
以上只加入第7行,有傳入參數的話,第一個shift取到的變數是類別本身的位址參照。
執行結果
3.5
3.46410161513775
bless的建構子
預設perl就沒有建構子的概念,也就是實體化類別後一定會執行的函式,但那不打緊,我們自寫一個new方法來當作建構子[3]。
Math.pm (+加入new這個副程式)
sub new { my $class = shift; my $self = { val1 => shift || 0, }; bless $self, $class; return $self; }
上面有提過,傳參第一項是類別本身,第4~6行是在new裡面定義一個參照,並取得實體化後傳進來的一個變數,第5行 || 是給預設值0的方法,如果要取多筆傳進來的值,可自行增加。
02.pl 改成 03.pl 內容如下:
03.pl
#!/usr/bin/perl -w use Cwd qw(abs_path); use lib abs_path; use Math; #my $obj={}; my $obj= Math->new(8,9,10); $obj->setAB(3,4); print $obj->ari; print "\n"; print $obj->geo; print "\n";
第7行在new呼叫時傳入3個數值,但只有第1個數值(8)被類別取用。第6行就作廢不必使用了,是不是方便很多?
如何使用類別中的自己
類別中如果想叫用類別自己的函式或變數,一般的程式語言都會有this或self的保留字,在perl的類別中,我們得自己定義出來。
只要借用上面的new函式,再把self定義為類別變數即可:
Math.pm (部分)
my $self; sub new { my $class = shift; $self = { val1 => shift || 0, }; bless $self, $class; return $self; } sub printVal1 { print $self->{'val1'}; }
第1行 把$self的定義移到函式外面。
第9行 把$self傳給叫用new的參照變數。
第13-15行 新增函式printVal1印出傳入的數值val1的方法。
以上的Math.pm類別中的兩個值也希望透過new的時候帶入的話,就得在new的時候把類別變數 $a, $b 指定,所以完整的 Math.pm 也要修改如下:
Math.pm
package Math; my $a; my $b; my $self; sub new { my $class = shift; $self = { a => shift || 0, b => shift || 0 }; $a = $self->{a}; $b = $self->{b}; bless $self, $class; return $self; } sub setAB { shift; $a=shift; $b=shift; } sub ari{ return ($a+$b)/2; } sub geo { return sqrt($a*$b); } 1;
類別基本沒變,但要注意的是第14、15行的$a和$b是類別的變數,而$self中的是參照中的變數,換言之,是雜湊參照。你也可以寫成這樣:
$a = $self->{'a'};
$b = $self->{'b'};
03.pl 改成 04.pl 如下:
04.pl
#!/usr/bin/perl -w use Cwd qw(abs_path); use lib abs_path; use Math; my $obj= Math->new(3,7); print $obj->ari; print "\n"; print $obj->geo; print "\n"; $obj->setAB(3,4); print $obj->ari; print "\n"; print $obj->geo; print "\n";
第6-10行 使用new函數帶入$a, $b 值
第12-16行 使用setAB函數帶入$a, $b 值
執行結果
5
4.58257569495584
3.5
3.46410161513775
能不能叫用類別中的變數?
04.pl中的函數透過new之後的參照變數 $obj就能叫用類別中的函式,例如第7,9行的叫用方式:
print $obj->ari;
print $obj->geo;
會呼叫類別中的函式。
但如果想要取用類別中的變數 $a 和$b,寫成下面可不可行?
print $obj->a; # Can't locate object method "a" via package "Math"
print $obj->$a; # Use of uninitialized value $a in method lookup
print $obj->'a'; # syntax error
print $obj->{a}; # 雖然這個有取到,但並不是類別的變數
print $obj::a; # Use of uninitialized value $obj::a
這裡的確有點麻煩,bless 有把類別封裝成一個物件,並把函式也包進來。但是類別中的變數是隱藏的傳導物質,你能透過函式來取用這些變數,但不能像某些語言一樣能直接取得,換句話說,你得設計 get/set 的函式。
為此我使用 dumper來觀察封裝後的$obj物件,並拿掉類別變數 $a $b改成全部使用雜湊參照$self中的變數。
Math.pm
package Math; my $self; sub new { shift; $self = { a => shift || 0, b => shift || 0 }; bless $self; return $self; } sub setAB { shift; $self->{'a'}=shift; $self->{'b'}=shift; } sub ari{ return ($self->{'a'}+$self->{'b'})/2; } sub geo { return sqrt($self->{'a'}*$self->{'b'}); } 1;
第7和12行 可以讓程式再精簡,畢竟$class是多餘的,就讓我們採用 bless的第3種寫法吧,我在前面有提到,第3種寫法就是自己。
因為拿掉了類別變數,所有的函式都採用雜湊參照變數,即$self->{'變數名'}
執行程式改成05.pl:
05.pl
#!/usr/bin/perl -w use Cwd qw(abs_path); use lib abs_path; use Math; use Data::Dumper qw(Dumper); my $obj= Math->new(2,5); print Dumper($obj); print $obj->ari; print "\n"; print $obj->geo;
第5和第8行可以印出$obj的內容,其餘不變。
執行結果
$VAR1 = bless( {
'b' => 5,
'a' => 2
}, 'Math' );
3.5
3.16227766016838
以上就是我對bless的理解。
結論
bless函式是perl寫類別的利器。
bless初始化的物件沒辦法直接叫用類別中的變數,所以建議捨棄不用,全部採用雜湊參照變數,如果覺得麻煩,也可以設定函式變數。
bless可以讓你的程式更簡潔,很適合寫大型的perl程式。
[3] 有提到解構子的寫法,這是我以前不知道的,只要在類別中加入DESTROY這個函式即可:
sub DESTROY { # DEFINE Destructors my $self = shift; print "Constructor Destroyed :P"; }
參考資料
[1] https://perldoc.perl.org/functions/bless
[2] https://www.cnblogs.com/yanzibuaa/p/7700832.html
[3] https://www.geeksforgeeks.org/perl-constructors-and-destructors/
[4] https://perlmaven.com/how-to-create-a-perl-module-for-code-reuse