perlobj
СОДЕРЖАНИЕ
- ИМЯ
- ОПИСАНИЕ
- Объект — это просто структура данных
- Класс — это просто пакет
- Метод — это просто подпрограмма
- Вызов метода
- Наследование
- Написание конструкторов
- Атрибуты
- Отступление о более умном и безопасном коде
- Варианты вызова метода
- Вызов методов класса
- bless, blessed и ref
- Класс UNIVERSAL
- AUTOLOAD
- Деструкторы
- Объекты, не являющиеся хешами
- Объекты «изнутри наружу»
- Псевдохеши
- СМОТРИТЕ ТАКЖЕ
ИМЯ
perlobj — ссылка на объект Perl
ОПИСАНИЕ
Этот документ содержит справку по возможностям объектно-ориентированного программирования в Perl. Если вы хотите ознакомиться с введением в объектно-ориентированное программирование в Perl, обратитесь к perlootut.
Для понимания объектов Perl сначала необходимо понять ссылки в Perl. Подробности см. в perlref.
В этом документе подробно описываются все объектно-ориентированные (ОО) возможности 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 содержит список родительских классов этого класса, если таковые имеются. Этот массив проверяется при разрешении метода, о чём мы поговорим позже.
Вызов методов из пакета означает, что он должен быть загружен, конечно, поэтому вы часто захотите загрузить модуль и добавить его в @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 возвращает истинные значения для всех ссылок, а не только для объектов.
Класс UNIVERSAL
Все классы автоматически наследуются от класса UNIVERSAL, который встроен в ядро Perl. Этот класс предоставляет ряд методов, которые можно вызывать как для класса, так и для объекта. Вы также можете переопределить некоторые из этих методов в своём классе. Если вы это сделаете, мы рекомендуем следовать описанной ниже встроенной семантике.
- 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.28.3/perlobj