神圣引用
Perl默認的面向?qū)ο笙到y(tǒng)真心小巧,只有3條規(guī)則:
- 一個類就是一個包
- 一個方法就是一個函數(shù)
- 一個引用(blessed,賜福)就是一個對象(我把bless過的引用翻譯成神圣引用,所以一個神圣引用就是一個對象)
由這3條規(guī)則,你可以造出任何東西。但僅僅靠這些來構建一個大型項目顯然太過簡約,特別它是缺乏元編程高度抽象的能力。對于超過了幾百行的(現(xiàn)代)程序,使用Moose是更好的選擇,然而還有大量的遺留代碼仍然使用的是默認的面向?qū)ο笙到y(tǒng)。
前2條規(guī)則,我們已經(jīng)在之前已經(jīng)介紹過了。內(nèi)置函數(shù)bless的作用就是將類名和引用聯(lián)系起來,之后該引用就成為一個有效的調(diào)用者,Perl就能在該引用上進行方法的調(diào)度。(通過引用聯(lián)系到類,調(diào)度類中的方法)
構造函數(shù)就是創(chuàng)建對象的方法(類方法)。按慣例,構造函數(shù)的名字是new(),但這個不是強制的。
bless有2個操作數(shù),一個引用和一個類名。引用可以是任意有效的引用,空引用也行;類名是可選項,默認為當前包名;bless的返回值就是bless過的引用(神圣引用)。一個簡單構造函數(shù):
sub new
{
my $class = shift;
bless {}, $class;
}
這個構造函數(shù)將調(diào)用者取出來作為類名。你也可以硬編碼類名,但那樣就不靈活了。帶參數(shù)的構造函數(shù)在繼承、委托或?qū)С龅臅r候能重用。
通過引用的類型就能了解對象實例是如何存儲自己數(shù)據(jù)的。哈希引用最為常見,但是其他類型的引用也是可以bless成對象的:
my $array_obj = bless [], $class;
my $scalar_obj = bless \$scalar, $class;
my $func_obj = bless \&some_func, $class;
Moose的類定義需要進行屬性的聲明,但Perl的默認OO系統(tǒng)要求沒那么嚴。一個表示籃球運動員的類,存儲球衣號碼和位置,它的構造函數(shù)可能是這樣的:
package Player
{
sub new
{
my ($class, %attrs) = @_;
bless \%attrs, $class;
}
}
創(chuàng)建新球員就是這樣的:
my $joel = Player->new( number => 10, position => 'center' );
my $damian = Player->new( number => 0, position => 'guard' );
它的類方法可以像訪問哈希元素一樣直接訪問對象屬性:
sub format
{
my $self = shift;
return '#' . $self->{number} . ' plays ' . $self->{position};
}
這樣有個問題就是:修改對象的內(nèi)部數(shù)據(jù)可能會破壞其他的代碼,相比而言使用訪問器的方式就更加安全。
sub number { return shift->{number} }
sub position { return shift->{position} }
現(xiàn)在你不得不自己動手編寫那些Moose免費為你提供的功能了。Moose鼓勵人們使用訪問器、設置器,而不是直接操作對象屬性。
方法調(diào)度和繼承
給定一個神圣引用$joel,這樣調(diào)用其方法:
my $number = $joel->number;
首先找到類(類跟引用聯(lián)系起來了)$joel,在這里是Player類。下一步Perl會在Player里面去找一個叫number()的函數(shù)。如果函數(shù)不存在,并且Player繼承了一個父類,那么Perl就去父類里面找,再往父類的父類里找,依次類推,直到找到number(),只要找到(任何地方),就以$joel 為調(diào)用者調(diào)用該函數(shù)。
CPAN上有個模塊namespace::autoclean可以幫助你避免因?qū)牒瘮?shù)而引起的名字沖突。
Moose提供了extends來跟蹤繼承關系,Perl則使用包全局變量@ISA來跟蹤。方法調(diào)度就是在每一個類的@ISA里面去找父類。如果InjuredPlayer 類繼承 Player類,你可以這樣寫:
package InjuredPlayer
{
@InjuredPlayer::ISA = 'Player';
}
或使用編譯指令parent,寫法會更簡單:
package InjuredPlayer
{
use parent 'Player';
}
Moose因為有自己的元模型存儲繼承信息,所以會擁有更多的元編程機會。
你可以繼承多個父類:
package InjuredPlayer
{
use parent qw( Player Hospital::Patient );
}
AUTOLOAD
如果一直沒有找到要調(diào)用的方法,那么Perl就會轉(zhuǎn)而去尋找AUTOLOAD()方法(之前講過的)。你可能意識到了,某些情況下問題會變得復雜。在多重繼承中它到底會調(diào)用哪個AUTOLOAD()方法呢?
重寫方法
默認OO支持重寫方法,但它沒有提供機制來讓你表明:****你要重寫父類方法****。導致的結果就是,任何你定義、聲明、或?qū)氲阶宇惖暮瘮?shù)都會重寫父類中的同名方法!
要重寫方法,聲明一個同名的方法即可,在重寫的方法內(nèi)部,可以用SUPER::來調(diào)用父類方法:
sub overridden
{
my $self = shift;
warn 'Called overridden() in child!';
return $self->SUPER::overridden( @_ );
}
SUPER::前綴就是告訴方法調(diào)度器去父類調(diào)用。你可以提供自定義參數(shù),但一般是@_,記得要將調(diào)用者卸載掉?。ˊ_的第一個參數(shù))。
SUPER::有個不好的特性就是,如果你從其他包導入方法,Perl有可能找不到正確的父類。 因為兼容性,這個特性一直保留著。CPAN上的SUPER模塊提供了一種解決方法。
Moose沒有這樣問題。
應對神圣引用的策略
默認OO系統(tǒng)(神圣引用)小巧但混亂,相比而言Mosse更容易使用,所以應該盡可能的選擇Moose。如果你必須要維護一些使用神圣引用的代碼,或者你還沒能說服你的團隊整體遷移到Moose上來,這里有些建議,可以幫助你避免一些坑:
- 在同一個類中不要混合函數(shù)和方法
- 盡量每個類使用一個.pm文件
- 遵循默認OO系統(tǒng)的標準,比如構造函數(shù)是new(),$self就是調(diào)用者的名字
- 使用訪問器,即使是在方法中。 Class::Accessor這個模塊可能對你有用
- 避免使用AUTOLOAD()
- 要考慮別人或者別的地方會使用你的類,bless時使用2個參數(shù),將類拆成最小的行為單元。
- 使用模塊來幫助你重用代碼,如 Role::Tiny