NEXT 也可能指函式:next

內容

名稱

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::methodmro 自 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 透過 CD 兩次繼承 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.