Spec-Zone.ru › Perl 5.30

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 делает), так что перераспределение генерирует исключение, если не находит метод «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      

который меняет порядок вызова методов.

В любом случае, фактические методы вызываются в том же контексте (список, скаляр или void), что и исходный вызов через EVERY, и возвращают:

  • В контексте списка — хеш ссылок на массивы. Каждый элемент хеша имеет полное имя метода в качестве ключа и ссылку на массив, содержащий значения возвращаемые методом в контексте списка, в качестве значения.

  • В скалярном контексте — ссылка на хеш скалярных значений. Каждый элемент хеша имеет полное имя метода в качестве ключа и значения возвращаемые методом в скалярном контексте в качестве значения.

  • Ничего в контексте void (очевидно).

Использование методов 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–2020 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.30.3/NEXT

Spec-Zone.ru

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