Spec-Zone.ru › Perl 5.34

perlobj

СОДЕРЖАНИЕ

  • ИМЯ
  • ОПИСАНИЕ
    • Объект — это просто структура данных
      • Объекты освящены; переменные — нет
    • Класс — это просто пакет
    • Метод — это просто подпрограмма
    • Вызов метода
    • Наследование
      • Как разрешается SUPER
      • Многократное наследование
      • Порядок разрешения методов
      • Кэширование порядка разрешения методов
    • Написание конструкторов
    • Атрибуты
      • Написание аксессоров
    • Несколько слов о более умном и безопасном коде
    • Варианты вызова метода
      • Имена методов с полным квалифицированным именем
      • Имена методов в виде строк
      • Имена классов в виде строк
      • Ссылки на подпрограммы в качестве методов
      • Вызов метода с разыменованием
      • Вызовы методов для дескрипторов файлов
    • Вызов методов класса
      • Синтаксис косвенного объекта
    • bless, blessed и ref
    • Класс UNIVERSAL
    • AUTOLOAD
    • Деструкторы
      • Глобальное уничтожение
    • Объекты, не являющиеся хешами
    • Объекты «снаружи внутрь»
    • Псевдо-хеши
  • СМОТРИТЕ ТАКЖЕ

ИМЯ

perlobj — ссылка на объект Perl

ОПИСАНИЕ

Этот документ предоставляет справочную информацию по объектно-ориентированным возможностям Perl. Если вы ищете введение в объектно-ориентированное программирование на Perl, обратитесь к perlootut.

Для понимания объектов Perl необходимо сначала понять ссылки в Perl. Подробности см. в perlreftut.

В данном документе подробно описываются все возможности объектно-ориентированного программирования (ООП) Perl. Если вы просто хотите написать собственный объектно-ориентированный код, вам, вероятно, будет лучше воспользоваться одним из объектных систем из CPAN, о которых рассказывается в perlootut.

Если вы хотите написать свою собственную объектную систему или вам нужно поддерживать код, который реализует объекты с нуля, этот документ поможет вам понять, как именно Perl реализует объектно-ориентированное программирование.

Несколько основных принципов определяют объектно-ориентированный Perl:

  1. Объект — это просто структура данных, которая знает, к какому классу она принадлежит.

  2. Класс — это просто пакет. Класс предоставляет методы, которые ожидают работы с объектами.

  3. Метод — это просто подпрограмма, которая ожидает в качестве первого аргумента ссылку на объект (или имя пакета для методов класса).

Давайте подробно рассмотрим каждый из этих принципов.

Объект — это просто структура данных

В отличие от многих других языков с поддержкой объектно-ориентированного программирования, Perl не предоставляет специального синтаксиса для создания объекта. Объекты — это просто структуры данных Perl (хеши, массивы, скаляры, дескрипторы файлов и т. д.), которые явно связаны с определенным классом.

Эта явная связь создается встроенной функцией bless, которая обычно используется в подпрограмме конструктора класса.

Вот простой конструктор:

package File;

sub new {
    my $class = shift;

    return bless {}, $class;
}

Имя new не является специальным. Мы могли бы назвать наш конструктор иначе:

package File;

sub load {
    my $class = shift;

    return bless {}, $class;
}

Современная конвенция для модулей ООП — всегда использовать new в качестве имени конструктора, но это не обязательно. Любая подпрограмма, которая освящает структуру данных в класс, является допустимым конструктором в Perl.

В предыдущих примерах код {} создает ссылку на пустой анонимный хеш. Функция bless затем принимает эту ссылку и связывает хеш с классом в $class. В простейшем случае переменная $class будет содержать строку "File".

Мы также можем использовать переменную для хранения ссылки на структуру данных, которая освящается как наш объект:

sub new {
    my $class = shift;

    my $self = {};
    bless $self, $class;

    return $self;
}

После освящения хеша, на который ссылается $self, мы можем начать вызывать методы на нем. Это полезно, если вы хотите разместить инициализацию объекта в отдельном методе:

sub new {
    my $class = shift;

    my $self = {};
    bless $self, $class;

    $self->_initialize();

    return $self;
}

Поскольку объект также является хешем, вы можете обращаться с ним как с хешем, используя его для хранения данных, связанных с объектом. Как правило, код внутри класса может рассматривать хеш как доступную структуру данных, в то время как код вне класса всегда должен рассматривать объект как непрозрачный. Это называется инкапсуляцией. Инкапсуляция означает, что пользователю объекта не нужно знать, как он реализован. Пользователь просто вызывает документированные методы объекта.

Обратите внимание, что (в отличие от большинства других языков ООП) Perl не обеспечивает или не навязывает инкапсуляцию каким-либо образом. Если вы хотите, чтобы объекты действительно были непрозрачными, вам нужно организовать это самостоятельно. Это можно сделать различными способами, включая использование «объектов „снаружи внутрь“» или модулей из CPAN.

Объекты освящены; переменные — нет

Когда мы освящаем что-либо, мы не освящаем переменную, которая содержит ссылку на эту вещь, и не освящаем ссылку, которую хранит переменная; мы освящаем вещь, на которую ссылается переменная (иногда известная как ссылочный объект). Это лучше всего демонстрируется следующим кодом:

use Scalar::Util 'blessed';

my $foo = {};
my $bar = $foo;

bless $foo, 'Class';
print blessed( $bar ) // 'not blessed';    # prints "Class"

$bar = "some other value";
print blessed( $bar ) // 'not blessed';    # prints "not blessed"

Когда мы вызываем bless для переменной, мы фактически освящаем лежащую в основе структуру данных, на которую ссылается переменная. Мы не освящаем саму ссылку, и не освящаем переменную, содержащую эту ссылку. Вот почему второй вызов blessed( $bar ) возвращает false. В этот момент $bar больше не хранит ссылку на объект.

Иногда вы можете увидеть в старых книгах или документации упоминание об «освящении ссылки» или описание объекта как «освященной ссылки», но это неверно. Это не ссылка освящается как объект; это то, на что ссылается ссылка (то есть, ссылаемый объект).

Класс — это просто пакет

Perl не предоставляет специального синтаксиса для определения класса. Пакет — это просто пространство имен, содержащее переменные и подпрограммы. Единственное различие заключается в том, что в классе подпрограммы могут ожидать ссылку на объект или имя класса в качестве первого аргумента. Это чисто вопрос соглашения, поэтому класс может содержать как методы, так и подпрограммы, которые не работают с объектом или классом.

Каждый пакет содержит специальный массив, называемый @ISA. Массив @ISA содержит список родительских классов этого класса, если таковые имеются. Этот массив проверяется при разрешении методов Perl, которое мы рассмотрим позже.

Вызов методов из пакета означает, что он должен быть загружен, конечно, поэтому вы часто захотите загрузить модуль и добавить его в @ISA одновременно. Вы можете сделать это в одном шаге, используя псевдоним parent. (В более старом коде вы можете встретить псевдоним base, который сегодня не рекомендуется, за исключением случаев, когда вам нужно работать с не рекомендуемым псевдонимом fields.)

Однако родительские классы устанавливаются, переменная пакета @ISA будет содержать список этих родителей. Это просто список скаляров, каждый из которых представляет собой строку, соответствующую имени пакета.

Все классы неявно наследуются от класса UNIVERSAL. Класс UNIVERSAL реализован ядром Perl и предоставляет несколько методов по умолчанию, таких как isa(), can(), и VERSION(). Класс UNIVERSAL никогда не появится в переменной пакета @ISA.

Perl только обеспечивает наследование методов как встроенную функцию. Наследование атрибутов оставляется на реализацию класса. Подробности см. в разделе «Написание аксессоров».

Метод — это просто подпрограмма

Perl не предоставляет специального синтаксиса для определения метода. Метод — это просто обычная подпрограмма, и объявляется с помощью sub. Что делает метод специальным, так это то, что он ожидает получения объекта или имени класса в качестве первого аргумента.

Perl предоставляет специальный синтаксис для вызова метода — оператор ->. Мы рассмотрим его подробнее позже.

Большинство методов, которые вы пишете, будут работать с объектами:

sub save {
    my $self = shift;

    open my $fh, '>', $self->path() or die $!;
    print {$fh} $self->data()       or die $!;
    close $fh                       or die $!;
}

Вызов метода

Вызов метода для объекта записывается как $object->method.

Левая часть оператора вызова метода (или стрелки) — это объект (или имя класса), а правая часть — имя метода.

my $pod = File->new( 'perlobj.pod', $data );
$pod->save();

Синтаксис -> также используется при разыменовании ссылки. Он выглядит как тот же оператор, но это две разные операции.

Когда вы вызываете метод, элемент слева от стрелки передаётся в качестве первого аргумента методу. Это означает, что при вызове Critter->new(), метод new() получает строку "Critter" в качестве своего первого аргумента. При вызове $fred->speak(), переменная $fred передаётся в качестве первого аргумента методу speak().

Как и в любом подпрограмме Perl, все аргументы, переданные в @_, являются псевдонимами исходных аргументов. Это включает и сам объект. Если вы напрямую присваиваете значение $_[0], вы измените содержимое переменной, хранящей ссылку на объект. Мы рекомендуем не делать этого, если вы точно не знаете, что делаете.

Perl определяет, в каком пакете находится метод, глядя на левую часть стрелки. Если левая часть — имя пакета, Perl ищет метод в этом пакете. Если левая часть — объект, тогда Perl ищет метод в пакете, в который был освящён объект.

Если левая часть ни имя пакета, ни объект, то вызов метода вызовет ошибку, но см. раздел "Варианты вызова методов" для более тонких нюансов.

Наследование

Мы уже говорили об особом массиве @ISA и прагме parent.

Когда класс наследуется от другого класса, все методы, определённые в родительском классе, доступны дочернему классу. Если вы пытаетесь вызвать метод на объекте, который не определён в его собственном классе, Perl также ищет этот метод в любом родительском классе, который у него может быть.

package File::MP3;
use parent 'File';    # sets @File::MP3::ISA = ('File');

my $mp3 = File::MP3->new( 'Andvari.mp3', $data );
$mp3->save();

Так как мы не определили метод save() в классе File::MP3, Perl обратится к родительским классам класса File::MP3 для поиска метода save(). Если Perl не найдёт метод save() нигде в иерархии наследования, он завершит работу.

В этом случае он находит метод save() в классе File. Обратите внимание, что объект, переданный в save() в этом случае, по-прежнему является объектом File::MP3, даже если метод найден в классе File.

Мы можем переопределить метод родительского класса в дочернем классе. Когда мы это делаем, мы всё ещё можем вызвать метод родительского класса с помощью псевдокласса SUPER.

sub save {
    my $self = shift;

    say 'Prepare to rock';
    $self->SUPER::save();
}

Модификатор SUPER может только использоваться для вызовов методов. Вы не можете использовать его для обычных вызовов подпрограмм или методов класса:

SUPER::save($thing);     # FAIL: looks for save() sub in package SUPER

SUPER->save($thing);     # FAIL: looks for save() method in class
                         #       SUPER

$thing->SUPER::save();   # Okay: looks for save() method in parent
                         #       classes

Как разрешается SUPER

Псевдокласс SUPER разрешается из пакета, где происходит вызов. Он не разрешается на основе класса объекта. Это важно, потому что это позволяет методам на разных уровнях глубокой иерархии наследования правильно вызывать свои соответствующие родительские методы.

package A;

sub new {
    return bless {}, shift;
}

sub speak {
    my $self = shift;

    say 'A';
}

package B;

use parent -norequire, 'A';

sub speak {
    my $self = shift;

    $self->SUPER::speak();

    say 'B';
}

package C;

use parent -norequire, 'B';

sub speak {
    my $self = shift;

    $self->SUPER::speak();

    say 'C';
}

my $c = C->new();
$c->speak();

В этом примере мы получим следующий вывод:

A
B
C

Это демонстрирует, как разрешается SUPER. Даже если объект освящён в классе C, метод speak() в классе B всё ещё может вызвать SUPER::speak() и ожидать, что он правильно обратится к родительскому классу B (т.е. классу, в котором находится вызов метода), а не к родительскому классу C (т.е. классу, к которому принадлежит объект).

Есть редкие случаи, когда это пакетное разрешение может быть проблемой. Если вы копируете подпрограмму из одного пакета в другой, разрешение SUPER будет выполнено на основе исходного пакета.

Многократное наследование

Многократное наследование часто указывает на проблему проектирования, но Perl всегда даёт вам достаточно верёвки, чтобы повеситься, если вы этого попросите.

Чтобы объявить несколько родителей, вам просто нужно передать несколько имён классов в use parent.

package MultiChild;

use parent 'Parent1', 'Parent2';

Порядок разрешения методов

Порядок разрешения методов важен только в случае множественного наследования. В случае единичного наследования Perl просто поднимается по цепочке наследования, чтобы найти метод:

Grandparent
  |
Parent
  |
Child

Если мы вызываем метод на объекте Child и этот метод не определён в классе Child, Perl будет искать этот метод в классе Parent, а затем, при необходимости, в классе Grandparent.

Если Perl не найдёт метод ни в одном из этих классов, он завершит работу с сообщением об ошибке.

Когда класс имеет нескольких родителей, порядок поиска метода становится более сложным.

По умолчанию Perl выполняет поиск метода по глубине слева направо. Это означает, что он начинается с первого родителя в массиве @ISA, а затем ищет всех его родителей, дедушек и т. д. Если метод не найден, он переходит к следующему родителю в исходном массиве @ISA класса и ищет оттуда.

          SharedGreatGrandParent
          /                    \
PaternalGrandparent       MaternalGrandparent
          \                    /
           Father        Mother
                 \      /
                  Child

Таким образом, учитывая диаграмму выше, Perl будет искать Child, Father, PaternalGrandparent, SharedGreatGrandParent, Mother и, наконец, MaternalGrandparent. Это может быть проблемой, потому что теперь мы ищем в SharedGreatGrandParent до проверки всех его производных классов (т.е. до того, как мы попытались Mother и MaternalGrandparent).

Можно запросить другой порядок разрешения методов с помощью прагмы mro.

package Child;

use mro 'c3';
use parent 'Father', 'Mother';

Эта прагма позволяет переключиться на порядок разрешения «C3». Проще говоря, порядок «C3» гарантирует, что общие родительские классы никогда не проверяются до дочерних классов, поэтому Perl теперь будет искать: Child, Father, PaternalGrandparent, Mother MaternalGrandparent, и, наконец, SharedGreatGrandParent. Однако обратите внимание, что это не поиск «по ширине»: все предки Father (кроме общего предка) проверяются до того, как проверяются какие-либо предки Mother.

Порядок C3 также позволяет вызывать методы в классах-сёстрах с помощью псевдокласса next. Подробнее об этой функции см. документацию по прагме mro.

Кэширование разрешения методов

Когда Perl ищет метод, он кеширует поиск, так что будущие вызовы метода не нуждаются в повторном поиске. Изменение родительского класса класса или добавление подпрограмм в класс приведут к аннулированию кэша для этого класса.

Прагма mro предоставляет некоторые функции для прямого управления кэшем методов.

Написание конструкторов

Как мы уже упоминали ранее, Perl не предоставляет специального синтаксиса для конструкторов. Это означает, что класс должен реализовывать свой собственный конструктор. Конструктор — это просто метод класса, который возвращает ссылку на новый объект.

Конструктор также может принимать дополнительные параметры, определяющие объект. Давайте напишем настоящий конструктор для класса File, который мы использовали ранее:

package File;

sub new {
    my $class = shift;
    my ( $path, $data ) = @_;

    my $self = bless {
        path => $path,
        data => $data,
    }, $class;

    return $self;
}

Как видите, мы сохранили путь и данные файла в самом объекте. Помните, что под капотом этот объект всё ещё является просто хешем. Позже мы напишем аксессоры для обработки этих данных.

Для нашего класса File::MP3 мы можем проверить, заканчивается ли переданный путь на ".mp3":

package File::MP3;

sub new {
    my $class = shift;
    my ( $path, $data ) = @_;

    die "You cannot create a File::MP3 without an mp3 extension\n"
        unless $path =~ /\.mp3\z/;

    return $class->SUPER::new(@_);
}

Этот конструктор позволяет родительскому классу выполнять фактическое создание объекта.

Атрибуты

Атрибут — это данные, принадлежащие конкретному объекту. В отличие от большинства объектно-ориентированных языков, Perl не предоставляет специального синтаксиса или поддержки для объявления и управления атрибутами.

Атрибуты часто хранятся в самом объекте. Например, если объект является анонимным хешем, мы можем сохранить значения атрибутов в хеше, используя имя атрибута в качестве ключа.

Хотя можно напрямую ссылаться на эти ключи хеша вне класса, считается лучшей практикой обернуть весь доступ к атрибуту методами аксессоров.

Это имеет несколько преимуществ. Аксессоры упрощают изменение реализации объекта в будущем, сохраняя при этом исходный API.

Аксессор позволяет добавлять дополнительный код вокруг доступа к атрибуту. Например, вы можете применить значение по умолчанию к атрибуту, который не был задан в конструкторе, или вы можете проверить, является ли новое значение атрибута допустимым.

Наконец, использование аксессоров упрощает наследование. Подклассы могут использовать аксессоры вместо того, чтобы знать, как реализован родительский класс внутри.

Написание аксессоров

Как и в случае с конструкторами, Perl не предоставляет специального синтаксиса для объявления аксессоров, поэтому классы должны явно обеспечивать написанные аксессорные методы. Есть два основных типа аксессоров: только для чтения и для чтения/записи.

Простой аксессор только для чтения просто получает значение одного атрибута:

sub path {
    my $self = shift;

    return $self->{path};
}

Аксессор для чтения/записи позволит вызывающей стороне установить значение, а также получить его:

sub path {
    my $self = shift;

    if (@_) {
        $self->{path} = shift;
    }

    return $self->{path};
}

Отступление о более умном и безопасном коде

Наш конструктор и аксессоры не очень умны. Они не проверяют, определён ли $path, а также не проверяют, является ли $path допустимым путём к файлу в файловой системе.

Выполнение этих проверок вручную может быстро стать утомительным. Написание множества аксессоров вручную также невероятно утомительно. На CPAN есть множество модулей, которые могут помочь вам написать более безопасный и лаконичный код, включая модули, которые мы рекомендуем в perlootut.

Варианты вызова методов

Perl поддерживает несколько других способов вызова методов помимо используемого нами способа $object->method().

Имена методов с полным квалифицированным именем

Perl позволяет вызывать методы с помощью их полного квалифицированного имени (имя пакета и имя метода):

my $mp3 = File::MP3->new( 'Regin.mp3', $data );
$mp3->File::save();

При вызове метода с полным квалифицированным именем, например File::save, поиск метода save начинается в классе File, минуя любой метод save, который может определить класс File::MP3. Он всё ещё ищет в родительских классах класса File при необходимости.

Хотя эта функция чаще всего используется для явного вызова методов, унаследованных от предкового класса, нет никаких технических ограничений, которые это запрещали:

my $obj = Tree->new();
$obj->Dog::bark();

Это вызывает метод bark из класса Dog на объекте класса Tree, даже если два класса полностью несвязаны. Используйте это с большой осторожностью.

Псевдокласс SUPER, описанный ранее, не является тем же, что и вызов метода с полным квалифицированным именем. Подробнее см. в предыдущем разделе "Наследование".

Имена методов как строки

Perl позволяет использовать скалярную переменную, содержащую строку, в качестве имени метода:

my $file = File->new( $path, $data );

my $method = 'save';
$file->$method();

Это работает точно так же, как вызов $file->save(). Это может быть очень полезно для написания динамического кода. Например, это позволяет передавать имя метода в качестве параметра другому методу.

Имена классов в виде строк

Perl также позволяет использовать скаляр, содержащий строку, в качестве имени класса:

my $class = 'File';

my $file = $class->new( $path, $data );

Опять же, это позволяет создавать очень динамический код.

Ссылки на подпрограммы как методы

Вы также можете использовать ссылку на подпрограмму в качестве метода:

my $sub = sub {
    my $self = shift;

    $self->save();
};

$file->$sub();

Это точно эквивалентно записи $sub->($file). Вы можете встретить этот идиоматический фрагмент в дикой природе в сочетании с вызовом can:

if ( my $meth = $object->can('foo') ) {
    $object->$meth();
}

Вызов метода с разыменованием

Perl также позволяет использовать разыменованную скалярную ссылку в вызове метода. Это довольно сложно, поэтому давайте рассмотрим пример кода:

$file->${ \'save' };
$file->${ returns_scalar_ref() };
$file->${ \( returns_scalar() ) };
$file->${ returns_ref_to_sub_ref() };

Это работает, если разыменование приводит к строке или ссылке на подпрограмму.

Вызовы методов для дескрипторов файлов

Внутри Perl, дескрипторы файлов являются экземплярами класса IO::Handle или IO::File. После открытия дескриптора файла вы можете вызывать методы для него. Кроме того, вы можете вызывать методы для дескрипторов файлов STDIN, STDOUT, и STDERR.

open my $fh, '>', 'path/to/file';
$fh->autoflush();
$fh->print('content');

STDOUT->autoflush();

Вызов методов класса

Поскольку Perl позволяет использовать ключевые слова для имён пакетов и имён подпрограмм, иногда он неправильно интерпретирует значение ключевого слова. Например, конструкция Class->new() может быть интерпретирована как 'Class'->new() или Class()->new(). На русском, второе толкование означает «вызвать подпрограмму с именем Class(), а затем вызвать new() в качестве метода на результате вызова Class()». Если в текущем пространстве имён есть подпрограмма с именем Class(), Perl всегда будет интерпретировать Class->new() как второй вариант: вызов new() на объекте, возвращённом вызовом Class().

Вы можете заставить Perl использовать первое толкование (т.е. как вызов метода для класса с именем «Class») двумя способами. Во-первых, вы можете добавить :: к имени класса:

Class::->new()

Perl всегда будет интерпретировать это как вызов метода.

В качестве альтернативы, вы можете заключить имя класса в кавычки:

'Class'->new()

Конечно, если имя класса находится в скаляре, Perl также сделает всё правильно:

my $class = 'Class';
$class->new();

Синтаксис косвенного объекта

За пределами случая с дескриптором файла использование этого синтаксиса не рекомендуется, так как он может сбить с толку интерпретатор Perl. Более подробная информация ниже.

Perl поддерживает другой синтаксис вызова методов, называемый «косвенной объектной» нотацией. Этот синтаксис называется «косвенным», потому что метод предшествует объекту, на котором он вызывается.

Этот синтаксис может использоваться с любым классом или методом объекта:

my $file = new File $path, $data;
save $file;

Мы рекомендуем избегать этого синтаксиса по нескольким причинам.

Во-первых, его может быть трудно читать. В приведённом выше примере не ясно, является ли save методом, предоставляемым классом File, или просто подпрограммой, которая ожидает в качестве первого аргумента объект файла.

Когда он используется с методами класса, проблема становится ещё хуже. Поскольку Perl позволяет писать имена подпрограмм как ключевые слова, Perl должен угадать, является ли ключевое слово после метода именем класса или именем подпрограммы. Другими словами, Perl может интерпретировать синтаксис как File->new( $path, $data ) или new( File( $path, $data ) ).

Для разбора этого кода Perl использует эвристику, основанную на увиденных им именах пакетов, имеющихся подпрограммах в текущем пакете, ранее увиденных ключевых словах и других входных данных. Нечего и говорить, что эвристики могут давать очень неожиданные результаты!

Более старая документация (и некоторые модули CPAN) поощряли этот синтаксис, особенно для конструкторов, поэтому вы можете его встретить в дикой природе. Однако мы рекомендуем избегать его использования в новом коде.

Вы можете заставить Perl интерпретировать ключевое слово как имя класса, добавив к нему «::», как мы видели ранее:

my $file = new File:: $path, $data;

bless, blessed, и ref

Как мы видели ранее, объект — это просто структура данных, освящённая в класс с помощью функции bless. Функция bless может принимать один или два аргумента:

my $object = bless {}, $class;
my $object = bless {};

В первом случае анонимный хеш освящается в класс в $class. Во втором случае анонимный хеш освящается в текущий пакет.

Второй вариант настоятельно не рекомендуется, потому что он нарушает способность подкласса повторно использовать конструктор родительского класса, но вы всё ещё можете встретить его в существующем коде.

Если вы хотите узнать, ссылается ли определённый скаляр на объект, вы можете использовать функцию blessed из модуля Scalar::Util, который поставляется с ядром Perl.

use Scalar::Util 'blessed';

if ( defined blessed($thing) ) { ... }

Если $thing ссылается на объект, эта функция возвращает имя пакета, в который освящён объект. Если $thing не содержит ссылки на освящённый объект, функция blessed возвращает undef.

Обратите внимание, что blessed($thing) также вернёт false, если $thing освящён в класс с именем «0». Это возможно, но довольно патологично. Не создавайте класс с именем «0», если вы не знаете, что делаете.

Аналогично, встроенная функция Perl ref специально обрабатывает ссылку на освящённый объект. Если вы вызываете ref($thing) и $thing содержит ссылку на объект, она вернёт имя класса, в который освящён объект.

Если вам просто нужно проверить, содержит ли переменная ссылку на объект, мы рекомендуем использовать defined blessed($object), так как ref возвращает true для всех ссылок, а не только для объектов.

Класс UNIVERSAL

Все классы автоматически наследуют от встроенного в ядро Perl класса UNIVERSAL. Этот класс предоставляет ряд методов, которые можно вызывать как для класса, так и для объекта. Вы также можете переопределить некоторые из этих методов в своём классе. Если вы это сделаете, мы рекомендуем следовать встроенной семантике, описанной ниже.

isa($class)

Метод isa возвращает true, если объект является членом класса $class, или членом подкласса $class.

Если вы переопределите этот метод, он не должен выбрасывать исключение.

DOES($role)

Метод DOES возвращает true, если его объект заявляет, что выполняет роль $role. По умолчанию это эквивалентно isa. Этот метод предназначен для использования расширениями объектной системы, реализующими роли, такие как Moose и Role::Tiny.

Вы также можете переопределить DOES непосредственно в своих классах. Если вы переопределите этот метод, он не должен выбрасывать исключение.

can($method)

Метод can проверяет, есть ли у класса или объекта метод с именем $method. Он проверяет наличие метода в классе и во всех его родителях. Если метод существует, возвращается ссылка на подпрограмму. Если нет, возвращается undef.

Если ваш класс отвечает на вызовы методов через AUTOLOAD, вам может потребоваться перегрузить can для возврата ссылки на подпрограмму для методов, которые обрабатывает ваш метод AUTOLOAD.

Если вы переопределите этот метод, он не должен выбрасывать исключение.

VERSION($need)

Метод VERSION возвращает номер версии класса (пакета).

Если аргумент $need задан, он проверяет, что текущая версия (как определено переменной $VERSION в пакете) больше или равна $need; в противном случае он выдаст ошибку. Этот метод вызывается автоматически в форме VERSION use.

use Package 1.2 qw(some imported subs);
# implies:
Package->VERSION(1.2);

Мы рекомендуем использовать этот метод для доступа к версии другого пакета, а не к непосредственному просмотру $Package::VERSION. Пакет, на который вы смотрите, может переопределить метод VERSION.

Мы также рекомендуем использовать этот метод для проверки достаточности версии модуля. Внутренняя реализация использует модуль version для правильного сравнения различных типов номеров версий.

AUTOLOAD

Если вы вызываете метод, который не существует в классе, Perl выдаст ошибку. Однако, если этот класс или любой из его родительских классов определяет метод AUTOLOAD, вызывается этот метод AUTOLOAD вместо него.

AUTOLOAD вызывается как обычный метод, и вызывающий не увидит разницы. Любое значение, которое возвращает ваш метод AUTOLOAD, возвращается вызывающему.

Полное имя метода, который был вызван, доступно в глобальной переменной пакета $AUTOLOAD для вашего класса. Поскольку это глобальная переменная, если вы хотите использовать её без префикса имени пакета в strict 'vars', вам нужно её объявить.

# XXX - this is a terrible way to implement accessors, but it makes
# for a simple example.
our $AUTOLOAD;
sub AUTOLOAD {
    my $self = shift;

    # Remove qualifier from original method name...
    my $called =  $AUTOLOAD =~ s/.*:://r;

    # Is there an attribute of that name?
    die "No such attribute: $called"
        unless exists $self->{$called};

    # If so, return it...
    return $self->{$called};
}

sub DESTROY { } # see below

Без объявления our $AUTOLOAD, этот код не будет компилироваться с pragma strict.

Как сказано в комментарии, это не лучший способ реализации аксессоров. Он медленный и чересчур хитроумный. Однако вы можете встретить его как способ реализации аксессоров в старом Perl-коде. Обратитесь к perlootut за рекомендациями по разработке объектно-ориентированного кода на Perl.

Если ваш класс имеет метод AUTOLOAD, мы настоятельно рекомендуем переопределить can в вашем классе также. Ваш переопределённый метод can должен возвращать ссылку на подпрограмму для любого метода, к которому отвечает ваш метод AUTOLOAD.

Деструкторы

Когда исчезает последняя ссылка на объект, объект уничтожается. Если у вас есть только одна ссылка на объект, хранящаяся в лексической скалярной переменной, объект уничтожается, когда эта переменная выходит из области видимости. Если вы сохранили объект в глобальной переменной пакета, этот объект может не выйти из области видимости до завершения программы.

Если вы хотите выполнить действие при уничтожении объекта, вы можете определить метод DESTROY в своём классе. Этот метод всегда будет вызываться Perl в соответствующее время, если только метод не пустой.

Он вызывается как любой другой метод, с объектом в качестве первого аргумента. Он не получает дополнительных аргументов. Однако переменная $_[0] будет только для чтения в деструкторе, поэтому вы не можете присвоить ей значение.

Если ваш метод DESTROY вызывает исключение, это не приведёт к передаче управления за пределы метода. Исключение будет сообщено STDERR как предупреждение, помеченное "(в очистке)", и Perl продолжит выполнение прерванной задачи.

Поскольку методы DESTROY могут вызываться в любое время, необходимо локализовать любые глобальные переменные состояния, которые могут быть установлены в вашем методе DESTROY. Если у вас есть сомнения относительно конкретной переменной состояния, не помешает её локализовать. Существует пять глобальных переменных состояния, и самый безопасный способ — локализовать все пять из них:

sub DESTROY {
    local($., $@, $!, $^E, $?);
    my $self = shift;
    ...;
}

Если вы определите AUTOLOAD в своём классе, Perl вызовет ваш AUTOLOAD для обработки метода DESTROY. Вы можете предотвратить это, определив пустой DESTROY, как мы сделали в примере автозагрузки. Вы также можете проверить значение $AUTOLOAD и вернуть значение без выполнения каких-либо действий при вызове для обработки DESTROY.

Глобальное уничтожение

Порядок уничтожения объектов во время глобального уничтожения перед завершением программы непредсказуем. Это означает, что любые объекты, содержащиеся в вашем объекте, могут уже быть уничтожены. Необходимо проверить, определён ли содержащийся объект, прежде чем вызывать метод на нём:

sub DESTROY {
    my $self = shift;

    $self->{handle}->close() if $self->{handle};
}

Можно использовать переменную ${^GLOBAL_PHASE} для определения, находитесь ли вы в настоящее время в фазе глобального уничтожения:

sub DESTROY {
    my $self = shift;

    return if ${^GLOBAL_PHASE} eq 'DESTRUCT';

    $self->{handle}->close();
}

Обратите внимание, что эта переменная была добавлена в Perl 5.14.0. Если вы хотите определить фазу глобального уничтожения в более старых версиях Perl, вы можете использовать модуль Devel::GlobalDestruction на CPAN.

Если ваш метод DESTROY выдает предупреждение во время глобального уничтожения, интерпретатор Perl добавит строку « во время глобального уничтожения» к предупреждению.

Во время глобального уничтожения Perl всегда собирает мусор объектов перед освобождением ссылок без благословения. Дополнительную информацию о глобальном уничтожении см. в "PERL_DESTRUCT_LEVEL" в perlhacktips.

Объекты, не являющиеся хэшами

Все предыдущие примеры демонстрировали объекты на основе благословенного хэша. Однако можно благословить любой тип структуры данных или ссылку, включая скаляры, глобы и подпрограммы. Вы можете столкнуться с таким кодом при анализе кода из различных источников.

Вот пример модуля как благословенного скаляра:

package Time;

use strict;
use warnings;

sub new {
    my $class = shift;

    my $time = time;
    return bless \$time, $class;
}

sub epoch {
    my $self = shift;
    return $$self;
}

my $time = Time->new();
print $time->epoch();

Объекты «внутрь-наружу»

В прошлом сообщество Perl экспериментировало с техникой, называемой «объектами внутрь-наружу». Объект «внутрь-наружу» хранит свои данные вне ссылки объекта, индексируя их по уникальной характеристике объекта, такой как его адрес памяти, а не внутри самого объекта. Это имеет преимущество в обеспечении инкапсуляции атрибутов объекта, так как данные не хранятся в самом объекте.

Эта техника была популярна какое-то время (и рекомендовалась в Perl Best Practices Дамиана Конвея), но никогда не получила всеобщего распространения. Модуль Object::InsideOut на CPAN предоставляет полную реализацию этой техники, и вы можете встретить его или другие модули «внутрь-наружу» в различных источниках кода.

Вот простой пример этой техники, используя ядро модуля Hash::Util::FieldHash. Этот модуль был добавлен в ядро для поддержки реализаций объектов «внутрь-наружу».

package Time;

use strict;
use warnings;

use Hash::Util::FieldHash 'fieldhash';

fieldhash my %time_for;

sub new {
    my $class = shift;

    my $self = bless \( my $object ), $class;

    $time_for{$self} = time;

    return $self;
}

sub epoch {
    my $self = shift;

    return $time_for{$self};
}

my $time = Time->new;
print $time->epoch;

Псевдо-хэши

Функция псевдо-хэша была экспериментальной функцией, представленной в более ранних версиях Perl и удалённой в 5.10.0. Псевдо-хэш — это ссылка на массив, к которому можно обращаться с использованием именованных ключей, как к хэшу. Вы можете столкнуться с кодом, использующим его. Для получения дополнительной информации см. прагму fields.

СМОТРИТЕ ТАКЖЕ

Более подробный и понятный учебник по объектно-ориентированному программированию на Perl можно найти в perlootut. Также рекомендуется ознакомиться с perlmodlib для получения руководств по созданию модулей и классов.

© 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/perlobj

Spec-Zone.ru

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