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() этого не может.
Типичное использование - в деструкторах иерархии классов, как показано в SYNOPSIS выше. Каждый класс в иерархии имеет метод DESTROY, который выполняет некоторое действие, специфичное для класса, а затем перераспределяет вызов по иерархии. В результате, когда уничтожается объект класса S, вызываются деструкторы всех его родительских классов (в порядке следования слева направо и сверху вниз).
Другое типичное использование перераспределения - в методах AUTOLOAD. Если такой метод определит, что он не может обработать конкретный вызов, он может перераспределить этот вызов в надежде, что какой-то другой AUTOLOAD (выше или слева) справится лучше.
По умолчанию, если попытка перераспределения не находит другой метод в иерархии классов объекта, она молча отказывается и ничего не делает (но см. "Принудительное перераспределение"). Эта вежливость отличается от (обычно раздражающего) поведения SUPER, которое выбрасывает исключение, если не может перераспределить.
Обратите внимание, что любое использование перераспределения (включая AUTOLOAD) для метода с другим именем является ошибкой.
sub S::oops { print "oops!\n"; $_[0]->NEXT::other_method() } Принудительное перераспределение
Можно сделать NEXT перераспределять более требовательно (как SUPER ), так что перераспределение выбрасывает исключение, если не находит метод "next" для вызова.
Для этого вызовите перераспределение как:
$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–2021 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.34.0/NEXT