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–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.36.0/NEXT