Spec-Zone.ru › Perl 5.28

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

Spec-Zone.ru

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