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