perlobj
СОДЕРЖАНИЕ
- ИМЯ
- ОПИСАНИЕ
- Объект — это просто структура данных
- Класс — это просто пакет
- Метод — это просто подпрограмма
- Вызов метода
- Наследование
- Написание конструкторов
- Атрибуты
- Отступление о более умном и безопасном коде
- Варианты вызова методов
- Вызов методов класса
- bless, blessed и ref
- Класс UNIVERSAL
- AUTOLOAD
- Деструкторы
- Объекты, не являющиеся хешами
- Объекты «изнутри наружу»
- Псевдо-хеши
- СМОТРИТЕ ТАКЖЕ
ИМЯ
perlobj — ссылка на объект Perl
ОПИСАНИЕ
В данном документе описаны возможности Perl для объектно-ориентированного программирования. Если вы ищете введение в объектно-ориентированное программирование на Perl, обратитесь к perlootut.
Для понимания объектов Perl необходимо сначала понять ссылки в Perl. Подробности см. в perlreftut.
В этом документе подробно описаны все объектно-ориентированные (ОО) функции Perl. Если вы просто хотите написать собственный объектно-ориентированный код, вам, вероятно, лучше воспользоваться одним из объектных систем с CPAN, описанных в perlootut.
Если вы хотите написать свою собственную объектную систему или вам нужно поддерживать код, который реализует объекты с нуля, этот документ поможет вам понять, как именно Perl реализует объектно-ориентированное программирование.
Существует несколько основных принципов, определяющих объектно-ориентированный Perl:
-
Объект — это просто структура данных, которая знает, к какому классу она принадлежит.
-
Класс — это просто пакет. Класс предоставляет методы, которые ожидают работы с объектами.
-
Метод — это просто подпрограмма, которая ожидает ссылку на объект (или имя пакета для методов класса) в качестве первого аргумента.
Давайте рассмотрим каждый из этих принципов подробнее.
Объект — это просто структура данных
В отличие от многих других языков, поддерживающих объектно-ориентированное программирование, 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 ) возвращает ложь. В этот момент $bar больше не хранит ссылку на объект.
Иногда в старых книгах или документации вы можете видеть упоминание «благословения ссылки» или описание объекта как «благословлённой ссылки», но это неправильно. Не ссылка благословляется как объект; это то, на что ссылается ссылка (то есть ссылаемый объект).
Класс — это просто пакет
Perl не предоставляет специального синтаксиса для определений классов. Пакет — это просто пространство имён, содержащее переменные и подпрограммы. Единственное различие состоит в том, что в классе подпрограммы могут ожидать ссылку на объект или имя класса в качестве первого аргумента. Это чисто вопрос соглашения, поэтому класс может содержать как методы, так и подпрограммы, которые не работают с объектом или классом.
Каждый пакет содержит специальный массив, называемый @ISA. Массив @ISA содержит список родительских классов этого класса, если таковые имеются. Этот массив проверяется при разрешении метода, которое мы рассмотрим позже.
Вызов методов из пакета означает, что он должен быть загружен, конечно, поэтому вы часто захотите загрузить модуль и добавить его в @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 будет искать метод save() в родительских классах класса File::MP3. Если 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; Синтаксис косвенного объекта доступен только тогда, когда включена настройка "indirect". Она включена по умолчанию, но может быть отключена по запросу. Эта функция присутствует в более старых наборах функций, но была удалена из набора :5.36; поэтому объявление use VERSION версии v5.36 или выше также отключит эту функцию.
use v5.36;
# indirect object syntax is no longer available
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) также вернёт ложь, если $thing был благословлен в класс с именем "0". Это возможно, но довольно патологично. Не создавайте класс с именем "0", если вы не знаете, что делаете.
Аналогично, встроенная функция Perl ref обрабатывает ссылку на благословленный объект особым образом. Если вы вызываете ref($thing) и $thing содержит ссылку на объект, она вернёт имя класса, в который объект был благословлен.
Если вы просто хотите проверить, содержит ли переменная ссылку на объект, мы рекомендуем использовать defined blessed($object), так как ref возвращает истинные значения для всех ссылок, а не только для объектов.
Класс UNIVERSAL
Все классы автоматически наследуют от встроенного в Perl ядро класс UNIVERSAL. Этот класс предоставляет ряд методов, которые могут быть вызваны на классе или объекте. Вы также можете переопределить некоторые из этих методов в своём классе. Если вы это сделаете, мы рекомендуем следовать описанной ниже встроенной семантике.
- isa($class)
-
Метод
isaвозвращает истинное значение, если объект является членом класса в$class, или членом подкласса$class.Если вы переопределите этот метод, он никогда не должен вызывать исключение.
- DOES($role)
-
Метод
DOESвозвращает истинное значение, если объект заявляет, что выполняет роль$role. По умолчанию это эквивалентноisa. Этот метод предназначен для использования расширениями объектной системы, которые реализуют роли, такие какMooseиRole::Tiny.Вы также можете переопределить
DOESнапрямую в собственных классах. Если вы переопределите этот метод, он никогда не должен вызывать исключение. - can($method)
-
Метод
canпроверяет, имеет ли класс или объект метод с именем$method. Проверка выполняется в классе и во всех его родительских классах. Если метод существует, возвращается ссылка на подпрограмму. В противном случае возвращаетсяundef.Если ваш класс отвечает на вызовы методов через
AUTOLOAD, вы можете перегрузитьcanдля возврата ссылки на подпрограмму для методов, которые обрабатываются методомAUTOLOAD.Если вы переопределите этот метод, он никогда не должен вызывать исключение.
- VERSION($need)
-
Метод
VERSIONвозвращает номер версии класса (пакета).Если аргумент
$needуказан, он проверяет, что текущая версия (определенная переменной $VERSION в пакете) больше или равна$need; в противном случае будет вызвано сообщение об ошибке. Этот метод вызывается автоматически формойVERSIONuse.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, этот код не будет компилироваться с прагмой 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 v5.36;
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 v5.36;
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–2023 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.38.0/perlobj