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::fooD::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 ([email protected])

错误和烦人之处

因为它是一个模块,而不是解释器的一部分,所以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.