神圣引用
Perl默認(rèn)的面向?qū)ο笙到y(tǒng)真心小巧葛家,只有3條規(guī)則:
- 一個類就是一個包
- 一個方法就是一個函數(shù)
- 一個引用(blessed,賜福)就是一個對象(我把bless過的引用翻譯成神圣引用盲链,所以一個神圣引用就是一個對象)
由這3條規(guī)則东抹,你可以造出任何東西帕识。但僅僅靠這些來構(gòu)建一個大型項目顯然太過簡約潜腻,特別它是缺乏元編程高度抽象的能力。對于超過了幾百行的(現(xiàn)代)程序卡睦,使用Moose是更好的選擇宴胧,然而還有大量的遺留代碼仍然使用的是默認(rèn)的面向?qū)ο笙到y(tǒng)。
前2條規(guī)則表锻,我們已經(jīng)在之前已經(jīng)介紹過了恕齐。內(nèi)置函數(shù)bless的作用就是將類名和引用聯(lián)系起來,之后該引用就成為一個有效的調(diào)用者瞬逊,Perl就能在該引用上進(jìn)行方法的調(diào)度显歧。(通過引用聯(lián)系到類,調(diào)度類中的方法)
構(gòu)造函數(shù)就是創(chuàng)建對象的方法(類方法)确镊。按慣例士骤,構(gòu)造函數(shù)的名字是new(),但這個不是強(qiáng)制的蕾域。
bless有2個操作數(shù)拷肌,一個引用和一個類名。引用可以是任意有效的引用旨巷,空引用也行巨缘;類名是可選項,默認(rèn)為當(dāng)前包名契沫;bless的返回值就是bless過的引用(神圣引用)带猴。一個簡單構(gòu)造函數(shù):
sub new
{
my $class = shift;
bless {}, $class;
}
這個構(gòu)造函數(shù)將調(diào)用者取出來作為類名。你也可以硬編碼類名懈万,但那樣就不靈活了。帶參數(shù)的構(gòu)造函數(shù)在繼承靶病、委托或?qū)С龅臅r候能重用会通。
通過引用的類型就能了解對象實(shí)例是如何存儲自己數(shù)據(jù)的。哈希引用最為常見娄周,但是其他類型的引用也是可以bless成對象的:
my $array_obj = bless [], $class;
my $scalar_obj = bless \$scalar, $class;
my $func_obj = bless \&some_func, $class;
Moose的類定義需要進(jìn)行屬性的聲明涕侈,但Perl的默認(rèn)OO系統(tǒng)要求沒那么嚴(yán)。一個表示籃球運(yùn)動員的類煤辨,存儲球衣號碼和位置裳涛,它的構(gòu)造函數(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免費(fèi)為你提供的功能了端三。Moose鼓勵人們使用訪問器、設(shè)置器鹃彻,而不是直接操作對象屬性郊闯。
方法調(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來跟蹤繼承關(guān)系,Perl則使用包全局變量@ISA來跟蹤躬它。方法調(diào)度就是在每一個類的@ISA里面去找父類腾啥。如果InjuredPlayer 類繼承 Player類,你可以這樣寫:
package InjuredPlayer
{
@InjuredPlayer::ISA = 'Player';
}
或使用編譯指令parent冯吓,寫法會更簡單:
package InjuredPlayer
{
use parent 'Player';
}
Moose因?yàn)橛凶约旱脑P痛鎯^承信息倘待,所以會擁有更多的元編程機(jī)會。
你可以繼承多個父類:
package InjuredPlayer
{
use parent qw( Player Hospital::Patient );
}
AUTOLOAD
如果一直沒有找到要調(diào)用的方法,那么Perl就會轉(zhuǎn)而去尋找AUTOLOAD()方法(之前講過的)组贺。你可能意識到了凸舵,某些情況下問題會變得復(fù)雜。在多重繼承中它到底會調(diào)用哪個AUTOLOAD()方法呢失尖?
重寫方法
默認(rèn)OO支持重寫方法啊奄,但它沒有提供機(jī)制來讓你表明:****你要重寫父類方法****。導(dǎo)致的結(jié)果就是掀潮,任何你定義菇夸、聲明、或?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::有個不好的特性就是恶迈,如果你從其他包導(dǎo)入方法涩金,Perl有可能找不到正確的父類谱醇。 因?yàn)榧嫒菪裕@個特性一直保留著步做。CPAN上的SUPER模塊提供了一種解決方法副渴。
Moose沒有這樣問題。
應(yīng)對神圣引用的策略
默認(rèn)OO系統(tǒng)(神圣引用)小巧但混亂全度,相比而言Mosse更容易使用煮剧,所以應(yīng)該盡可能的選擇Moose。如果你必須要維護(hù)一些使用神圣引用的代碼将鸵,或者你還沒能說服你的團(tuán)隊整體遷移到Moose上來勉盅,這里有些建議,可以幫助你避免一些坑:
- 在同一個類中不要混合函數(shù)和方法
- 盡量每個類使用一個.pm文件
- 遵循默認(rèn)OO系統(tǒng)的標(biāo)準(zhǔn)顶掉,比如構(gòu)造函數(shù)是new()草娜,$self就是調(diào)用者的名字
- 使用訪問器,即使是在方法中痒筒。 Class::Accessor這個模塊可能對你有用
- 避免使用AUTOLOAD()
- 要考慮別人或者別的地方會使用你的類宰闰,bless時使用2個參數(shù),將類拆成最小的行為單元簿透。
- 使用模塊來幫助你重用代碼移袍,如 Role::Tiny