Наряду со ссылками и модулями в Perl версии 5.000 появились объекты. Как обычно, Perl не заставляет всех использовать единственно правильный стиль, а поддерживает несколько разных стилей. Благодаря этому люди решают свои задачи так, как им нравится.
При написании программ необязательно пользоваться объектами, в отличие от языка Java, где программы представляют собой экземпляры объектов. Однако при желании можно написать Perl-программу, в которой используется практичес ки весь арсенал приемов объектно
Вы можете выбрать ровно столько объектно-ориентированных принципов, сколько захочется. Связи (ties) являются единственной частью Perl, где объектно-ориентированный подход обязателен. Но даже здесь об этом должен знать липи программист, занимающийся реализ
Под капотом
Если спросить десятерых программистов, что такое "объектная ориентация", вы получите десять разных ответов. Люди рассуждают об "абстракции" и "инкапсуляции", пытаются выделить основные черты объектно-ориентированных языков
программирования и придумать для них умные термины, чтобы потом писать статьи и книги. Не все объектно-ориентированные языки обладают одинаковыми возможностями, но все они считаются объектно-ориентированными. Конечно, в результате появляются все новые ста
Мы будем использовать терминологию из документации Perl и страницы руководства ^ег/о&/(1). Объект представляет собой переменную, принадлежащую i;
некоторому классу. Методами называются функции, ассоциируемые с классом или объектом. В Perl класс представляет собой пакет - а обычно и модуль. Объект является ссылкой на что-то, что было приведено (blessed) к классу. Приведение ассоциирует субъект с кла
$object = {}; # Ссылка на хэш
bless($object, "Data::Encoder"); # Привести $object к классу
oft Data::Encoder bless($object); # Привести $object к текущему пакету Имя класса соответствует имени пакета (Data::Encoder в приведенном выше примере). Поскольку классы являются модулями (обычно), код класс;! Data::Encoder находится в файле Data/Encoder.рт. Структура каталогов, как и для традиционных модулей, существует
После приведения объекта вызов функции ref для ссылки на него возвращает имя класса вместо фундаментального типа субъекта:
$obj = [3,5];
print ref($obj), " ", $obj->[1], "\n";
bless($obj, "Human::Cannibal");
print ref($obj), " ", $obj->[1], "\n";
ARRAY 5 Human::Cannibal 5 Как видите, приведенную ссылку все еще можно разыменовать. Чаще нсего объекты реализуются с помощью приведенных ссылок на хэши. Вы можете использовать любые типы ссылок, но ссылки на хэш обеспечивают максимальную пакость. Они позволяют создавать в объ
$obj->{Stomach} = "Empty"; # Прямое обращение к данным объекта
$obj->{NAME} = "Thag";
# Символы верхнего регистра в имени поля
# помогают выделить его (необязательно)
Хотя Perl позволяет любому коду за пределами класса напрямую обращаться к данным объекта, это считается нежелательным. Согласно общепринятому мнеению, работа с данными должна вестись только с использованием методов, предназначенных для этой цели. У разраб
Методы
Для вызова методов используется оператор, оператор ->. В следующем примере мы вызываем метод encode () объекта $object с аргументом "data" и сохраняем возвращаемое значение в переменной $encoded:
$encoded = $object->encode("data");
Перед нами метод объекта, поскольку мы вызываем метод конкретного объекта. Также существуют методы классов, то есть методы, вызываемые по имени класса:
$encoded = Data::Encoder->encode("data"); При вызове метода вызывается функция соответствующего класса с неявно!'! передачей в качестве аргумента либо ссылки (для метода объекта), либо строки (для метода класса). В рецепте 13.17 показано, как вызывать методы с именами, определяемыми во время
В большинстве классов существуют специальные методы, возвращающие новые объекты - конструкторы. В отличие от некоторых объектно-ориентированных языков, конструкторы Perl не имеют специальных имен. В сущности, конструктор можно назвать, как вам захочется.
Типичный конструктор выглядит следующим образом:
sub new {
my $class = shift;
my $self = {}; # Выделить новый хэш для объекта
bless($selt, $class);
return $self;
Вызов конструктора выглядит так:
$object = Class->new(); Если дело обходится без наследования или иных выкрутасов, это фактически эквивалентно
$object = Class::new("Class"); Первым аргументом функции new() является имя класса, к которому приводится новая ссылка. Конструктор должен передать эту строку bless () в качестве второго аргумента.
В рецепте 13.1 также рассматриваются функции, возвращающие приведенные ссылки. Конструкторы не обязаны быть методами класса. Также встречаются методы объектов, возвращающие новые объекты (см. рецепт 13.6).
Деструктором называется функция, которая выполняется при уничтожении субъекта, соответствующего данному объекту, в процессе сборки мусора. В отличие от конструкторов имена деструкторов жестко фиксируются. Методу-деструктору должно быть присвоено имя DESTR
Некоторые языки на уровне синтаксиса позволяют компилятору ограничить доступ к методам класса. В Perl такой возможности нет - программа может вызывать любые методы объекта. Автор класса должен четко документировать открытые методы (те, которые можно испол
Perl не различает методы, вызываемые для класса (методы классов), и методы, вызываемые для объекта (методы экземпляров). Если вы хотите, чтобы некоторый метод вызывался только как метод класса, поступите следующим образом:
sub class_only_method {
my $class = shift;
die "class method called on object" if ref $class;
# Дополнительный код
}
Чтобы метод вызывался только как метод экземпляра, воспользуйтесь следующим кодом:
sub instance_only_method {
my $self = shift;
die "instance method called on class" unless ref $self;
# Дополнительный код
}
Если в вашей программе вызывается неопределенный метод объекта, Perl не будет жаловаться на стадии компиляции; вместо этого произойдет исключение во время выполнения. Аналогично, компилятор не перехватывает ситуации, при которой методу, который должен выз
Чтобы предотвратить инициирование исключений для неопределенных методов, можно использовать механизм AUTOLOAD для перехвата вызовов несуществующих методов. Данная возможность рассматривается в рецепте 13.11.
Наследование
Отношения наследования определяют иерархию классов. При вызове метода, не определенного в классе, поиск метода с указанным именем осуществляется и иерархии. Используется первый найденный метод. Наследование позволяет строить классы "на фундаменте" других
В некоторых языках существует специальный синтаксис наследования. В Perl каждый класс (пакет) может занести список своих суперклассов, то есть родителей в иерархии, в глобальную (не лексическую!) пакетную переменную @ISA. Этот список просматривается во вр
Если поиск унаследованного метода заканчивается неудачей, проверка выполняется заново, но на этот раз ищется метод с именем AUTOLOAD. Поиск метода $ob->meth(), где объект $ob принадлежит классу Р, происходит в следующей последовательности:
P::meth
Любой метод S: :meth() в пакетах S из @P::ISA, рекурсивно.
UNIVERSAL::meth Подпрограмма Р:: AUTOLOAD.
Любой метод S: :AUTOLOAD( ) в пакетах S из @P::ISA, рекурсивно.
Подпрограмма UNIVERSAL: : AUTOLOAD,
В большинстве классов массив @ISA состоит из одного элемента - такая ситуация называется одиночным наследованием. Если массив @ISA содержит несколько элементов, говорят, что класс реализует множественное наследование. Вокруг достоинств и недостатков м
В рецепте 13.9 рассматриваются основы наследования и базовые принципы построения классов, обеспечивающие удобство субклассирования. В рецепте 13.10 мы покажем, как субкласс переопределяет методы своих суперкласса.
Perl не поддерживает наследования данных. Класс может напрямую обращаться к данным другого класса, но делать этого не следует. Это не соответствует принципам инкапсуляции и нарушает абстракцию. Если вы последуете рекомендациям из рецептов 13.10 и 13.1
Косвенный вызов методов:
$lector = new Human::Cannibal;
feed $lector "Zak":
move $lector "New York"; представляет собой альтернативный вариант синтаксиса для:
$lector = Human::Cannibal->new();
$object->feed("Zak");
$object->move("New York"); Косвенный вызов методов привлекателен для англоязычных программистов и хорошо знаком программирующим на C++ (где подобным образом использует ся new). He поддавайтесь соблазну. Косвенный вызов обладает двумя существен ными недостатками. Во-первых, он д
printf STDERR "stuff here\n";
Эта позиция, если она заполняется, должна содержать простое слово, блок или имя скалярной переменной; скалярные выражения недопустимы. Это приводит к невероятно запутанным проблемам, как в двух следующих строках:
vmove $obj->{FIELD}; # Вероятно, ошибка
move $ary[$i]; # Вероятно, ошибка
Как ни странно, эти команды интерпретируются следующим образом:
$obj->move->{FIELD}; # Сюрприз!
$ary->move->[$i]; # Сюрприз!
вместо ожидаемого:
$obj->{FIELD}->move(); # Ничего подобного
$ary[$i]->move; # Ничего подобного Вторая проблема заключается в том, что во время компиляции Perl приходится гадать, что такое name и move - функции или методы. Обычно Perl угадывает правильно, но в случае ошибки функция будет откомпилирована как метод, и наоборот. Это может привести
Некоторые замечания по объектной терминологии
В объектно-ориентированном мире одни и те же концепции часто описываются разными словами. Если вы программировали на другом объектно-ориентированном языке, возможно, вам захочется узнать, как знакомые термины и концепции представлены в Perl.
Например, объекты часто называются экземплярами (instances) классов, а методы этих объектов - методами экземпляров. Поля данных, относящиеся к i .I.K-дому объекту, часто называются данными экземпляров или атрибутами объектов, а поля данных, общие для всех
Кроме того, термины базовый класс и суперкласс описывают одно и то /i>' понятие (родитель или другой предок в иерархии наследования), тогда как T( гмины производный класс и субкласс описывают противоположное отношение (непосредственный или отдаленный пото
Программисты на C++ привыкли использовать статические методы, виртуальные методы и методы экземпляров, но Perl поддерживает только методы fUlaccoe и методы объектов. В действительности в Perl существует только общее понятие "метод". Принадлежность мет
Программисты C++ привыкли к глобальным (то есть существующим на уровне класса) конструкторам и деструкторам. В Perl они идентичны соответственно инициализирующему коду модуля и блоку END{}.
С позиций C++ все методы Perl являются виртуальными. По этой причине их аргументы никогда не проверяются на соответствие прототипам функции, как это можно сделать для встроенных и пользовательских функций. Прототипы проверяются компилятором во время к
Философское отступление
В своих объектно-ориентированных аспектах Perl предоставляет полную свободу выбора: возможность делать одни и те же вещи несколькими способами (приведение позволяет создать объект из данных любого типа), возможности модификации классов, написанных дру
В менее гибких языках программирования обычно устанавливаются более жесткие ограничения. Многие языки с фанатичным упорством отстаивают закры-тость данных, проверку типов на стадии компиляции, сложные сигнатуры функций и другие возможности. Все эти во
Объекты Perl не плохи; просто они другие.
> Смотри также ------------------------------
В литературе по объектно-ориентированному программированию Perl упоминается очень редко. Изучение объектно-ориентированных аспектов языка лучше всего начать с документации Perl - особенно с учебника по объектам perltoot(l). За справочной информацией о
Необходимо предоставить пользователю возможность создания новых объектов.
Решение
Создайте конструктор. В Perl метод-конструктор не только инициализирует объект, но и предварительно выделяет память для него - как правило, с использованием анонимного хэша. Конструкторы C++, напротив, вызываются после выделения памяти. В объектно-ориенти
Канонический конструктор объекта в Perl выглядит так:
sub new {
my $class = shift;
my $self = {};
bless($self, $class);
return $self;
} Данный фрагмент эквивалентен следующей строке:
sub new { bless( { }, shift ) }
Комментарий
Любой метод, который выделяет память для объекта и инициализирует его, фактически является конструктором. Главное, о чем следует помнить, - ссылка становится объектом лишь после того, как для нее будет вызвана функция bless. Простейший, хотя и не особенно
sub new { bless({ }) } #Давайте включим в него инициализацию объекта:
sub new {
my $self ='{ }; # Выделить анонимный хэш bless($self);
# Инициализировать два атрибута/поля/переменных экземпляра
$self->{START} = time();
$self->{AGE} = 0;
return $self;
} Такой конструктор не очень полезен, поскольку в нем используется одноаргументная форма bless, которая всегда приводит объект в текущий пакет. Это означает, что полезное наследование от него становится невозможным; сконструированные объекты всегда буду
Проблема решается просто: достаточно организовать в конструкторе обработку первого аргумента. Для метода класса он представляет собой имя пакета. Передайте имя класса функции bless в качестве второго аргумента:
sub new {
my $classname = shift; # Какой класс мы конструируем?
my $self = {}; # Выделить память
bless($obref, $classname); # Привести к нужному типу
$self->{START} = time(); # Инициализировать поля данных
$self->{AGE} = 0;
return $obref; # И вернуть
} Теперь конструктор будет правильно наследоваться производными классами. Выделение памяти и приведение можно отделить от инициализации данных
экземпляра. В простых классах это не нужно, однако такое разделение упрощает
наследование; см. рецепт 13.10.
sub new {
my $classname = shift; # Какой класс мы конструируем?
my $self = {}; # Выделить память
bless($self, $classname); # Привести к нужному типу
$self->_init(@>_); # Вызвать _init
# с остальными аргументами
return $self;
}
# "Закрытый" метод для инициализации полей. Он всегда присваивает START
# текущее время, a AGE - 0, При вызове с аргументами _init
# интерпретирует их как пары ключ/значение и инициализирует ими объект.
sub _init {
my $self = shift;
$self->{START} = time();
$self->{AGE} = 0;
if (@_) {
my %extra = @_;
@$self{keys %extra} = values %extra;
}
}
> Смотри также ------------------------------
perltoot(1) и perlobj(1); рецепты 13.6; 13.9-13.10.
Некоторый фрагмент кода должен выполняться в случае, если надобность в объекте отпадает. Например, объект может использоваться в интерфейсе с внешним миром или содержать циклические структуры данных - в этих случаях он
должен "убрать за собой". При уничтожении объекта может происходить удалс иие временных файлов, разрыв циклических связей, корректное отсоединение <" сокета или уничтожение порожденных процессов.
Решение
Создайте метод с именем DESTROY. Он будет вызываться в том случае, когда n;i объект не остается ни одной ссылки или при завершении программы (в зависимости от того, что произойдет раньше). Освобождать память не нужно; лишь выполните все завершающие действ
sub DESTROY {
my $self = shift;
printf("$self dying at %s\n", scalar localtime);
}
Комментарий
У каждой истории есть начало и конец. История объекта начинается с выполнения конструктора, который явно вызывается при создании объекта. Жизненный цикл объекта завершается в деструкторе - методе, который неявно вызовется при уходе объекта из жизни. Весь
Почему деструктору нельзя присвоить произвольное имя, как это делается для конструктора? Потому что конструктор явно вызывается по имени, а деструктор -нет. Уничтожение объекта выполняется автоматически через систему сборки мусора Perl, реализация которой
Почему имя DESTROY пишется в верхнем регистре? В Perl это обозначение говорит о том, что данная функция вызывается автоматически. К числу других автоматически вызываемых функций принадлежат BEGIN, END, AUTOLOAD и все мето-^ ды связанных объектов (см.
Пользователь не должен беспокоиться о том, когда будет вызван конструктор Просто это произойдет в нужный момент. В языках, не поддерживающих сборк] мусора, программисту приходится явно вызывать деструктор для очистки пам" ти и сброса состояния - и над
Беднягу можно только пожалеть.
Благодаря автоматизированному управлению памятью в Perl деструкторы объеК тов используются редко. Но даже в случаях, когда они нужны, явный вызов деcтруктора - вещь не только излишняя, но и попросту опасная. Деструктор буде вызван системой времени исп
Система сборки мусора не поможет лишь в одной ситуации - при и ..'чин циклических ссылок в структуре данных:
$Self->{WHATEVER} = $self;
В этом случае циклическую ссылку приходится удалять вручную, чтобы при работе программы не возникали утечки памяти. Такой вариант чреват ошибками, но это лучшее, что мы можем сделать. Впрочем, в рецепте 13.13 представлено элегантное решение этой пробл
Метод DESTROY ne вызывается при завершении программы, вызванной функцией ехес.
> Смотри также -------------------------------
Для работы с каждым атрибутом данных объекта (иногда называемым переменной экземпляра или свойством) необходим специальный метод доступа. Как написать функцию для работы с данными экземпляра?
Решение
Напишите пару методов для чтения и присваивания соответствующего ключа в x:)iue объекта:
sub get_name {
my $self = shift;
return $self->{NAME};
}
sub set_name {
my $self = shift;
$self->{NAME} = shift;
} Или воспользуйтесь одним методом, который решает ту или иную задачу в зависимости от того, был ли передан аргумент при вызове:
sub name {
my $self = shift;
if (@_) < $self->{NAME} = shift } return $self->{NAME};
} Иногда при установке нового значения полезно вернуть старое:
sub age {
my $self = shift;
my $prev = $self->{AGE};
if (@_) { $self->{AGE} = shift }
return $prev;
}
# Пример одновременного чтения и записи атрибута
$obj->age( 1 + $obj->age );
Вам нужен метод, который вызывается для класса в целом, а не для отдельного объекта. Например, он может обрабатывать глобальный атрибут данных, общий для всех экземпляров класса.
Решение
Первым аргументом метода класса является не ссылка, как в методах объектон.:;
строка, содержащая имя класса. Методы классов работают с данными пакета, а иг данными объекта, как показывает приведенный ниже метод population:
package Person;
$Body_Count = 0;
sub population { return $Body_Count }
sub new { # Конструктор
$Body_Count++;
return bless({}, shift);
}
sub DESTROY { --$BodyCount } # Деструктор
# Позднее пользователь может написать:
package main;
for (1..10) { push @people, Person->new }
printf "There are %d people alive.\n", Person->population();
There are 10 people alive.
Комментарий
Обычно каждый объект обладает определенным состоянием, полная информация о котором хранится в самом объекте. Значение атрибута данных одного объекта никак не связано со значением этого атрибута в другом экземпляре того же класса. Например, присваивание ат
$him = Person->new();
$him->gender("male");
$her = Person->new();
$her->gender("female"); Представьте атрибут, общий для всего класса - изменение атрибута для одного экземпляра приводит к его изменению для остальных экземпляров. Подобно тому, как имена глобальных переменных часто записываются с большой буквы, некоторые программисты предпоч
FixedArray->Max_Bounds(100); # Устанавливается для всего класса
$alpha = FixedArray->new();
printf "Bound on alpha is %d\n", $alpha->Max_Bounds();
100
$beta = FixedArray->new();
$beta->Max_Bounds(50); # Также устанавливается для всего класса
printf "Bound on alpha is %d\n", $alpha->Max_Bounds();
50
Реализация выглядит просто:
package FixedArray;
$Bounds =7; # default
sub new { bless( {}, shift ) }
sub Max_Bounds {
my $proto = shift;
$Bounds = shift if @_; # Разрешить обновления
return $Bounds;
} Чтобы фактически сделать атрибут доступным только для чтения, просто удалите команды обновления:
sub Max_Bounds { $Bounds }
Настоящий параноик сделает $Bounds лексической переменной, которая ограничена областью действия файла, содержащего класс. В этом случае никто не сможет обратиться к данным класса через $FlxedArray: : Bounds. Работать с данными придется через интерфейсные
Следующий совет поможет вам строить расширяемые классы: храните данные объекта в пространстве имен объекта (в хэше), а данные класса - в пространстве имен класса (пакетные переменные или лексические переменные с файловой областью действия). Только методы
sub new {
my $cl'ass = shift;
my $self = bless({}, $class);
$self->{Max_Bounds_ref} = \$Bounds;
return $self;
}
> Смотри также --------------------------------
perltoot(1), perlobj{1) и perlbot(1); рецепт 13.3; пример использования метода places в разделе "Пример. Перегруженный класс FixNum" в рецепте 13.14.
Вы привыкли работать со структурированными типами данных - более сложными, чем массивы и хэши Perl (например, структуры С и записи Pascal). Вы слышали о том, что классы Perl не уступают им по возможностям, но не хотите изучать объектно-ориентированное про
Решение
Воспользуйтесь стандартным модулем Class::Struct для объявления С-подобных структур:
use Class::Struct; # Загрузить модуль построения структур
struct Person => { # Создать определение класса "Person"
name =>'$', # Имя - скаляр
аgе =>'$', # Возраст - тоже скаляр
peers => '@', # Но сведения о друзьях - массив (ссылка)
);
mу $р = Person->new(); # Выделить память для пустой структуры Person
$p->name("Jason Smythe"); # Задать имя
$p->age(13); # Задать возраст
$p->peers( ["Wilbur", "Ralph", "Fred" ] ); # Задать друзей
# Или так:
@{$p->peers} = ("Wilbur", "Ralph", "Fred");
# Выбрать различные значения, включая нулевого друга
printf "At age %d, %s's first friend is %s.\n", $p->age, $p->name, $p->peers(0);