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 ищет метод в этом пакете. Если элемент слева — объект, 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, этот код не будет компилироваться при использовании директивы 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–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.30.3/perlobj