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 ) возвращает 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 ищет метод в пакете, в который благословлён объект.
Если левая часть не является именем пакета или объектом, вызов метода вызовет ошибку, но см. раздел «Варианты вызова методов» для более тонких нюансов.
Наследование
Мы уже говорили об особом массиве @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; Синтаксис косвенного объекта доступен только когда включена функция с именем "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; в противном случае произойдёт аварийное завершение. Этот метод вызывается автоматически формой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, этот код не будет компилироваться с пragma 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 добавит строку « during global destruction» к предупреждению.
Во время глобального уничтожения 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 из ядра Perl. Этот модуль был добавлен в ядро для поддержки реализации объектов «внутренних объектов».
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–2021 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.36.0/perlobj