Spec-Zone.ru › Perl 5.38

NEXT

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • СИНОПСИС
  • ОПИСАНИЕ
    • Принуждение к повторному переадресации
    • Избежание повторений
    • Вызов всех версий метода одним вызовом
    • Использование методов EVERY
  • СМОТРИТЕ ТАКЖЕ
  • АВТОР
  • ОШИБКИ И НЕДОСТАТКИ
  • АВТОРСКИЕ ПРАВА

НАЗВАНИЕ

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 повторно переадресовывается, как если бы вызывающий метод изначально не был найден.

Примечание: перед использованием этого модуля, вы должны ознакомиться с next::method в модуле ядра mro. mro является модулем ядра с Perl 5.9.5.

Другими словами, вызов $self->NEXT::m() возобновляет поиск в глубину слева направо иерархии классов $self, который привел к первоначальному вызову m.

Обратите внимание, что это не то же самое, что $self->SUPER::m(), которое начинает новый поиск, ограниченный по поиску предков текущего класса. $self->NEXT::m() может возвращаться назад за пределами текущего класса — чтобы искать подходящий метод в других предках $self, в то время как $self->SUPER::m() не может.

Типичное применение — в деструкторах иерархии классов, как показано в синтаксисе выше. Каждый класс в иерархии имеет метод 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 наследует A::foo дважды — через C и D). В таких случаях последовательность повторных переадресаций 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();

вызывает каждый метод с именем foo, который унаследован объектом в $obj. То есть:

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 предшествует X в @B::ISA.

Как правило, нет необходимости беспокоиться о порядке вызовов. Они будут слева направо, по ширине, сначала — наиболее производные. Это прекрасно работает для большинства унаследованных методов (включая деструкторы), но неприемлемо для некоторых типов методов (таких как конструкторы, клонеры, отладчики и инициализаторы), где целесообразнее, чтобы методы наименее производные вызывались первыми (так как более производные методы могут полагаться на поведение своих «предков»). В этом случае вместо использования псевдокласса 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.

© 1993–2023 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.38.0/NEXT

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API