NEXT - 提供一個偽類別 NEXT(等)允許方法重新調度
use NEXT;
package P;
sub P::method { print "$_[0]: P method\n"; $_[0]->NEXT::method() }
sub P::DESTROY { print "$_[0]: P dtor\n"; $_[0]->NEXT::DESTROY() }
package Q;
use base qw( P );
sub Q::AUTOLOAD { print "$_[0]: Q AUTOLOAD\n"; $_[0]->NEXT::AUTOLOAD() }
sub Q::DESTROY { print "$_[0]: Q dtor\n"; $_[0]->NEXT::DESTROY() }
package R;
sub R::method { print "$_[0]: R method\n"; $_[0]->NEXT::method() }
sub R::AUTOLOAD { print "$_[0]: R AUTOLOAD\n"; $_[0]->NEXT::AUTOLOAD() }
sub R::DESTROY { print "$_[0]: R dtor\n"; $_[0]->NEXT::DESTROY() }
package S;
use base qw( Q R );
sub S::method { print "$_[0]: S method\n"; $_[0]->NEXT::method() }
sub S::AUTOLOAD { print "$_[0]: S AUTOLOAD\n"; $_[0]->NEXT::AUTOLOAD() }
sub S::DESTROY { print "$_[0]: S dtor\n"; $_[0]->NEXT::DESTROY() }
package main;
my $obj = bless {}, "S";
$obj->method(); # Calls S::method, P::method, R::method
$obj->missing_method(); # Calls S::AUTOLOAD, Q::AUTOLOAD, R::AUTOLOAD
# Clean-up calls S::DESTROY, Q::DESTROY, P::DESTROY, R::DESTROY
NEXT
模組會將一個名為 NEXT
的偽類別新增到任何使用它的程式中。如果方法 m
呼叫 $self->NEXT::m()
,則會重新調度對 m
的呼叫,就像呼叫方法原本未找到一樣。
注意:在使用此模組之前,您應該查看核心 mro 模組中的 next::method。mro
自 Perl 5.9.5 以來一直是核心模組。
換句話說,對 $self->NEXT::m()
的呼叫會繼續深度優先、由左至右搜尋 $self
的類別階層,這會導致對 m
的原始呼叫。
請注意,這與 $self->SUPER::m()
不同,後者會開始一個新的調度,僅限於搜尋目前類別的祖先。$self->NEXT::m()
可以回溯到目前類別之前,以在 $self
的其他祖先中尋找合適的方法,而 $self->SUPER::m()
則不能。
典型的用法會出現在類別階層的解構函式中,如上方的 SYNOPSIS 所示。階層中的每個類別都有 DESTROY 方法,用於執行特定類別的動作,然後重新發送呼叫至階層。因此,當類別 S 的物件被銷毀時,所有父類別的解構函式都會被呼叫(深度優先,由左至右的順序)。
重新發送的另一個典型用法會出現在 AUTOLOAD
的方法中。如果此類方法確定它無法處理特定呼叫,它可能會選擇重新發送該呼叫,希望其他 AUTOLOAD
(在其上方或左方)能處理得更好。
預設情況下,如果重新發送嘗試無法在物件類別階層的其他地方找到另一個方法,它會安靜地放棄並什麼都不做(但請參閱 "強制重新發送")。這種優雅的默許也不同於 SUPER
(通常令人討厭的)行為,如果它無法重新發送,它會擲回例外狀況。
請注意,任何方法(包括 AUTOLOAD
)嘗試重新發送任何名稱不同的方法都是一個致命錯誤。例如
sub S::oops { print "oops!\n"; $_[0]->NEXT::other_method() }
可以讓 NEXT
重新發送更具要求性(即像 SUPER
所做的那樣),以便如果找不到要呼叫的「下一個」方法,重新發送會擲回例外狀況。
為執行此動作,只需像這樣呼叫重新發送
$self->NEXT::ACTUAL::method();
而不是
$self->NEXT::method();
ACTUAL
告訴 NEXT
實際上必須有下一個要呼叫的方法,否則它應該擲回例外狀況。
NEXT::ACTUAL
最常使用在 AUTOLOAD
方法中,作為拒絕 AUTOLOAD
要求,但保留正常失敗時擲回例外狀況語意的方式
sub AUTOLOAD {
if ($AUTOLOAD =~ /foo|bar/) {
# handle here
}
else { # try elsewhere
shift()->NEXT::ACTUAL::AUTOLOAD(@_);
}
}
透過使用 NEXT::ACTUAL
,如果沒有其他 AUTOLOAD
來處理方法呼叫,將會擲回例外狀況(通常在沒有合適的 AUTOLOAD
時會發生)。
如果 NEXT
重新發送用於「菱形」類別階層的方法中
# A B
# / \ /
# C D
# \ /
# E
use NEXT;
package A;
sub foo { print "called A::foo\n"; shift->NEXT::foo() }
package B;
sub foo { print "called B::foo\n"; shift->NEXT::foo() }
package C; @ISA = qw( A );
sub foo { print "called C::foo\n"; shift->NEXT::foo() }
package D; @ISA = qw(A B);
sub foo { print "called D::foo\n"; shift->NEXT::foo() }
package E; @ISA = qw(C D);
sub foo { print "called E::foo\n"; shift->NEXT::foo() }
E->foo();
然後衍生類別可能會透過兩個或更多不同的路徑(重新)繼承基底類別方法(例如 E
透過 C
和 D
兩次繼承 A::foo
的方式)。在這種情況下,一系列 NEXT
重新發送會呼叫多重繼承的方法,次數與其繼承的次數一樣多。例如,上述程式碼會列印
called E::foo
called C::foo
called A::foo
called D::foo
called A::foo
called B::foo
(例如,A::foo
會呼叫兩次)。
在某些情況下,這可能是菱形階層中的預期效果,但在其他情況下(例如,對於解構式),在重新調度順序中只呼叫每個方法一次可能會更適當。
要涵蓋此類情況,您可以透過以下方式重新調度方法:
$self->NEXT::DISTINCT::method();
而不是
$self->NEXT::method();
這會導致重新調度程式只拜訪每個不同的 method
方法一次。也就是說,跳過重新調度期間已拜訪過的階層中的任何類別。因此,例如,如果重新撰寫前一個範例
package A;
sub foo { print "called A::foo\n"; shift->NEXT::DISTINCT::foo() }
package B;
sub foo { print "called B::foo\n"; shift->NEXT::DISTINCT::foo() }
package C; @ISA = qw( A );
sub foo { print "called C::foo\n"; shift->NEXT::DISTINCT::foo() }
package D; @ISA = qw(A B);
sub foo { print "called D::foo\n"; shift->NEXT::DISTINCT::foo() }
package E; @ISA = qw(C D);
sub foo { print "called E::foo\n"; shift->NEXT::DISTINCT::foo() }
E->foo();
則會列印
called E::foo
called C::foo
called A::foo
called D::foo
called B::foo
並省略對 A::foo
的第二次呼叫(因為它不會與對 A::foo
的第一次呼叫不同)。
請注意,您也可以使用
$self->NEXT::DISTINCT::ACTUAL::method();
或
$self->NEXT::ACTUAL::DISTINCT::method();
以取得唯一的呼叫和失敗時例外處理。
請注意,為了與歷史相容,您也可以使用 NEXT::UNSEEN
取代 NEXT::DISTINCT
。
NEXT
提供的另一個偽類別是 EVERY
。它的行為比 NEXT
系列簡單得多。呼叫
$obj->EVERY::foo();
會呼叫物件 $obj
中繼承的每個名為 foo
的方法。也就是說
use NEXT;
package A; @ISA = qw(B D X);
sub foo { print "A::foo " }
package B; @ISA = qw(D X);
sub foo { print "B::foo " }
package X; @ISA = qw(D);
sub foo { print "X::foo " }
package D;
sub foo { print "D::foo " }
package main;
my $obj = bless {}, 'A';
$obj->EVERY::foo(); # prints" A::foo B::foo X::foo D::foo
在方法呼叫前加上 EVERY::
會導致呼叫物件階層中具有該名稱的每個方法。如上例所示,它們並非以 Perl 通常的「最左深度優先」順序呼叫。相反地,它們以「廣度優先依賴順序」呼叫。
這表示物件的繼承樹是以廣度優先方式橫越,而產生的類別順序用作呼叫方法的順序。但是,該順序會透過強制規則進行修改,即派生類別的適當方法必須在任何祖先類別的相同方法之前呼叫。這就是為什麼在上例中,X::foo
會在 D::foo
之前呼叫,即使 D
在 @B::ISA
中出現在 X
之前。
一般來說,不需要擔心呼叫順序。它們將會從左到右、廣度優先、最派生優先。這對於大多數繼承方法(包括解構式)來說都能完美運作,但對於某些類型的函式(例如建構式、複製器、偵錯器和初始化器)並不適當,因為在這些情況下,最不派生的方法應該先呼叫(因為更派生的方法可能依賴於其「祖先」的行為)。在這種情況下,請改用 EVERY
偽類別
$obj->EVERY::foo(); # prints" A::foo B::foo X::foo D::foo
您可以使用 EVERY::LAST
偽類別
$obj->EVERY::LAST::foo(); # prints" D::foo X::foo B::foo A::foo
它會反轉方法呼叫順序。
無論使用哪個版本,實際方法都會在與透過 EVERY
進行的原始呼叫相同的內容(清單、純量或空值)中呼叫,並傳回
清單內容中陣列參考的雜湊。雜湊的每個項目都有完全限定的方法名稱作為其金鑰,以及對包含方法的清單內容傳回值的陣列的參考作為其值。
純量內容中純量值的雜湊的參考。雜湊的每個項目都有完全限定的方法名稱作為其金鑰,以及方法的純量內容傳回值作為其值。
在空內容中沒有任何東西(很明顯)。
EVERY
方法使用 EVERY
呼叫的典型方式是將其包覆在另一個所有類別都繼承的基礎方法中。例如,為了確保物件繼承的每個解構函式實際上都被呼叫(而不是只有最左邊、深度優先的解構函式)
package Base;
sub DESTROY { $_[0]->EVERY::Destroy }
package Derived1;
use base 'Base';
sub Destroy {...}
package Derived2;
use base 'Base', 'Derived1';
sub Destroy {...}
等等。每個衍生類別需要自己的清除行為,只需新增自己的 Destroy
方法(不是 DESTROY
方法),然後繼承的解構函式中對 EVERY::LAST::Destroy
的呼叫就會正確地接收它。
同樣地,要建立一個類別階層,其中每個由新物件繼承的初始化函式都會被呼叫
package Base;
sub new {
my ($class, %args) = @_;
my $obj = bless {}, $class;
$obj->EVERY::LAST::Init(\%args);
}
package Derived1;
use base 'Base';
sub Init {
my ($argsref) = @_;
...
}
package Derived2;
use base 'Base', 'Derived1';
sub Init {
my ($argsref) = @_;
...
}
等等。每個衍生類別需要一些額外的初始化行為,只需新增自己的 Init
方法(不是 new
方法),然後繼承的建構函式中對 EVERY::LAST::Init
的呼叫就會正確地接收它。
mro(特別是 next::method),自 Perl 5.9.5 以來一直是核心模組。
Damian Conway (damian@conway.org)
由於 NEXT
是模組,而不是解釋器的組成部分,因此它必須猜測在方法查詢順序中找到周圍呼叫的位置。在菱形繼承模式下,它偶爾會猜錯。
它也過於緩慢(儘管有快取)。
歡迎提供意見、建議和補丁程式。
Copyright (c) 2000-2001, Damian Conway. All Rights Reserved.
This module is free software. It may be used, redistributed
and/or modified under the same terms as Perl itself.