Приглашаем посетить
Орловка (orlovka.niv.ru)

Классы, объекты и связи

Назад
Глава 13 Классы, объекты и связи
Вперед

Классы, объекты и связи

Введение

Наряду со ссылками и модулями в Perl версии 5.000 появились объекты. Как обычно, Perl не заставляет всех использовать единственно правильный стиль, а поддерживает несколько разных стилей. Благодаря этому люди решают свои задачи так, как им нравится. При написании программ необязательно пользоваться объектами, в отличие от языка Java, где программы представляют собой экземпляры объектов. Однако при желании можно написать Perl-программу, в которой используется практичес ки весь арсенал приемов объектно-ориентированного программирования. В Perl поддерживаются классы и объекты, одиночное и множественное наследование, методы экземпляров и методы классов, переопределение методов, конструкторы и деструкторы, перегрузка операторов, методы-посредники с автозагрузкой, делегирование, иерархия объектов и два уровня сборки мусора. Вы можете выбрать ровно столько объектно-ориентированных принципов, сколько захочется. Связи (ties) являются единственной частью Perl, где объектно-ориентированный подход обязателен. Но даже здесь об этом должен знать липи программист, занимающийся реализацией модуля; случайный пользователь остается в блаженном неведении относительно внутренних механизмов. Связи, рассматриваемые в рецепте 13.14, позволяют организовать прозрачный перехват обращений к переменной. Например, с помощью связей можно создать хэш с возможностью поиска по ключу или по значению.

Под капотом

Если спросить десятерых программистов, что такое "объектная ориентация", вы получите десять разных ответов. Люди рассуждают об "абстракции" и "инкапсуляции", пытаются выделить основные черты объектно-ориентированных языков программирования и придумать для них умные термины, чтобы потом писать статьи и книги. Не все объектно-ориентированные языки обладают одинаковыми возможностями, но все они считаются объектно-ориентированными. Конечно, в результате появляются все новые статьи и книги. Мы будем использовать терминологию из документации Perl и страницы руководства ^ег/о&/(1). Объект представляет собой переменную, принадлежащую i; некоторому классу. Методами называются функции, ассоциируемые с классом или объектом. В Perl класс представляет собой пакет - а обычно и модуль. Объект является ссылкой на что-то, что было приведено (blessed) к классу. Приведение ассоциирует субъект с классом. Для этого используется функция bles^ вызываемая с одним или двумя аргументами. Первым аргументом является ссылк.1 на приводимый объект, а необязательным вторым аргументом - пакет, к которому осуществляется приведение.
$object = {}; # Ссылка на хэш
bless($object, "Data::Encoder"); # Привести $object к классу
oft Data::Encoder bless($object); # Привести $object к текущему пакету

Имя класса соответствует имени пакета (Data::Encoder в приведенном выше примере). Поскольку классы являются модулями (обычно), код класс;! Data::Encoder находится в файле Data/Encoder.рт. Структура каталогов, как и для традиционных модулей, существует исключительно для удобства; она никак не связана с наследованием, ограничением доступа к переменным или чем-нибудь еще. Однако в отличие от традиционных модулей, объектные модули очень редко используют Exporter. Вся работа должна вестись только через вызовы методов, но не через импортированные функции или переменные. После приведения объекта вызов функции 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 не имеют специальных имен. В сущности, конструктор можно назвать, как вам захочется. Программисты C++ обожают присваивать своим конструкторам в Perl имя new. Мы рекомендуем выбирать имя конструктора так, чтобы оно имело смысл в контексте решаемой задачи. Например, конструкторы расширения Tk в Perl называются по именам создаваемых ими элементов (widgets). Менее распространенный подход заключается в экспортировании функции, имя которой совпадает с именем класса; см. пример в разделе "Пример. Перегруженный класс StrNum" в рецепте 13.14. Типичный конструктор выглядит следующим образом:
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). Деструктором называется функция, которая выполняется при уничтожении субъекта, соответствующего данному объекту, в процессе сборки мусора. В отличие от конструкторов имена деструкторов жестко фиксируются. Методу-деструктору должно быть присвоено имя DESTROY. Этот метод, если он существует, вызывается для всех объектов непосредственно перед освобождением памяти. Наличие деструктора (см. рецепт 13.2) необязательно. Некоторые языки на уровне синтаксиса позволяют компилятору ограничить доступ к методам класса. В 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 не будет жаловаться на стадии компиляции; вместо этого произойдет исключение во время выполнения. Аналогично, компилятор не перехватывает ситуации, при которой методу, который должен вызываться для простых чисел, передается комплексный аргумент. Метод представляет собой обычный вызов функции, пакет которой определяется во время выполнения. Методы, как и все косвенные функции, не имеют проверки прототипа - проверка выполняется на стадии компиляции. Даже если бы вызовы методов учитывали наличие прототипа, в Perl компилятор не сможет автоматически установить точный тип или интервал аргумента функции. Прототипы Perl предназначены для форсирования контекста аргумента функции, а не для проверки интервала. Странности прототипов Perl описаны в рецепте 10.11. Чтобы предотвратить инициирование исключений для неопределенных методов, можно использовать механизм AUTOLOAD для перехвата вызовов несуществующих методов. Данная возможность рассматривается в рецепте 13.11.

Наследование

Отношения наследования определяют иерархию классов. При вызове метода, не определенного в классе, поиск метода с указанным именем осуществляется и иерархии. Используется первый найденный метод. Наследование позволяет строить классы "на фундаменте" других классов, чтобы код не приходилось переписывать заново. Классы являются одной из форм многократного использования кода и потому способствуют развитию Лени - главной добродетели программиста. В некоторых языках существует специальный синтаксис наследования. В Perl каждый класс (пакет) может занести список своих суперклассов, то есть родителей в иерархии, в глобальную (не лексическую!) пакетную переменную @ISA. Этот список просматривается во время выполнения программы, при вызове метода, не определенного в классе объекта. Если первый пакет, указанный в @ISA, не содержит искомого метода, но имеет собственный массив @ISA, то 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 содержит несколько элементов, говорят, что класс реализует множественное наследование. Вокруг достоинств и недостатков множественного наследования идут постоянные споры, но Perl поддерживает эту возможность. В рецепте 13.9 рассматриваются основы наследования и базовые принципы построения классов, обеспечивающие удобство субклассирования. В рецепте 13.10 мы покажем, как субкласс переопределяет методы своих суперкласса.
Perl не поддерживает наследования данных. Класс может напрямую обращаться к данным другого класса, но делать этого не следует. Это не соответствует принципам инкапсуляции и нарушает абстракцию. Если вы последуете рекомендациям из рецептов 13.10 и 13.12, это ограничение не вызовет особых проблем.
Косвенный вызов методов:
$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 поддавайтесь соблазну. Косвенный вызов обладает двумя существен ными недостатками. Во-первых, он должен подчиняться тем же ненадежным при вилам, что и позиция файлового манипулятора в print и printf:
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 покажутся вам странными. Все странности происходят лишь от того, что вы привыкли к философии других языков. Объектно-ориентированная сторона Perl абсолютно разумна - если мыслить категориями Perl. Для любой задачи, которую нельзя решить на Perl по аналогии с Java или C++, найдется прекрасно работающее решение в идеологии Perl. Программист-параноик даже сможет обеспечить полную закрытость: в perltoot(l) рассказано о том, как с помощью приведения замыканий получить объекты, по степени закры-гости не уступающие объектам C++ (и даже превосходящие их). Объекты Perl не плохи; просто они другие.
> Смотри также ------------------------------
В литературе по объектно-ориентированному программированию Perl упоминается очень редко. Изучение объектно-ориентированных аспектов языка лучше всего начать с документации Perl - особенно с учебника по объектам perltoot(l). За справочной информацией обращайтесь v.perlobj{1). Вероятно, этот документ понадобится вам при чтении руководства perlbot(1) полного объектно-ориентированных фокусов.

13.1. Конструирование объекта

Проблема

Необходимо предоставить пользователю возможность создания новых объектов.

Решение

Создайте конструктор. В Perl метод-конструктор не только инициализирует объект, но и предварительно выделяет память для него - как правило, с использованием анонимного хэша. Конструкторы C++, напротив, вызываются после выделения памяти. В объектно-ориентированном мире конструкторы 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, которая всегда приводит объект в текущий пакет. Это означает, что полезное наследование от него становится невозможным; сконструированные объекты всегда будут приводиться к классу, в котором была откомпилирована функция new. При наследовании этот класс не обязательно совпадете тем, для которого вызывался данный метод.
Проблема решается просто: достаточно организовать в конструкторе обработку первого аргумента. Для метода класса он представляет собой имя пакета. Передайте имя класса функции 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.

13.2. Уничтожение объекта

Проблема

Некоторый фрагмент кода должен выполняться в случае, если надобность в объекте отпадает. Например, объект может использоваться в интерфейсе с внешним миром или содержать циклические структуры данных - в этих случаях он должен "убрать за собой". При уничтожении объекта может происходить удалс иие временных файлов, разрыв циклических связей, корректное отсоединение <" сокета или уничтожение порожденных процессов.

Решение

Создайте метод с именем DESTROY. Он будет вызываться в том случае, когда n;i объект не остается ни одной ссылки или при завершении программы (в зависимости от того, что произойдет раньше). Освобождать память не нужно; лишь выполните все завершающие действия, которые имеют смысл для данного класса.
sub DESTROY {
my $self = shift;
printf("$self dying at %s\n", scalar localtime);
}

Комментарий

У каждой истории есть начало и конец. История объекта начинается с выполнения конструктора, который явно вызывается при создании объекта. Жизненный цикл объекта завершается в деструкторе - методе, который неявно вызовется при уходе объекта из жизни. Весь завершающий код, относящийся к объекту, помещается в деструктор, который должен называться DESTROY. Почему деструктору нельзя присвоить произвольное имя, как это делается для конструктора? Потому что конструктор явно вызывается по имени, а деструктор -нет. Уничтожение объекта выполняется автоматически через систему сборки мусора Perl, реализация которой в настоящее время основана на системе подсчета ссылок. Чтобы знать, какой метод должен вызываться при уничтожении объекта, Perl требует присвоить деструктору имя DESTROY. Если несколько объектов одновременно выходят из области действия, Perl не гарантирует вызова их деструкторов в определенном порядке.
Почему имя DESTROY пишется в верхнем регистре? В Perl это обозначение говорит о том, что данная функция вызывается автоматически. К числу других автоматически вызываемых функций принадлежат BEGIN, END, AUTOLOAD и все мето-^ ды связанных объектов (см. рецепт 13.15) - например, STORE и FETCH.
Пользователь не должен беспокоиться о том, когда будет вызван конструктор Просто это произойдет в нужный момент. В языках, не поддерживающих сборк] мусора, программисту приходится явно вызывать деструктор для очистки пам" ти и сброса состояния - и надеяться на то, что он не ошибся в выборе момент;
Беднягу можно только пожалеть.
Благодаря автоматизированному управлению памятью в Perl деструкторы объеК тов используются редко. Но даже в случаях, когда они нужны, явный вызов деcтруктора - вещь не только излишняя, но и попросту опасная. Деструктор буде вызван системой времени исполнения в тот момент, когда объект перестанет использоваться. В большинстве классов деструкторы не нужны, поскольку Perl сам решает основные проблемы - такие, как освобождение памяти. Система сборки мусора не поможет лишь в одной ситуации - при и ..'чин циклических ссылок в структуре данных:
$Self->{WHATEVER} = $self;
В этом случае циклическую ссылку приходится удалять вручную, чтобы при работе программы не возникали утечки памяти. Такой вариант чреват ошибками, но это лучшее, что мы можем сделать. Впрочем, в рецепте 13.13 представлено элегантное решение этой проблемы. Однако вы можете быть уверены, что при завершении программы будут вызваны деструкторы всех ее объектов. При завершении работы интерпретатора выполняется тотальная сборка мусора. Даже недоступные или циклические объекты не переживут последней чистки. Следовательно, можно быть уверенным в том, что объект когда-нибудь будет уничтожен должны образом, даже если выход из программы никогда не происходит. Если Perl работает внутри другого приложения, вторая форма сборки мусора встречается чаще (при каждом завершении интерпретатора). Метод DESTROY ne вызывается при завершении программы, вызванной функцией ехес.
> Смотри также -------------------------------

perltoot(1) и perlobj(1) рецепты 13.10; 13.13.

13.3. Работа с данными экземпляра

Проблема

Для работы с каждым атрибутом данных объекта (иногда называемым переменной экземпляра или свойством) необходим специальный метод доступа. Как написать функцию для работы с данными экземпляра?

Решение

Напишите пару методов для чтения и присваивания соответствующего ключа в 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 );

Комментарий

Работа методов зависит от того, как вы организуете открытый интерфейс к объекту Нормальный класс не любит, чтобы окружающие копались у него во внутренностях. Для каждого атрибута данных должен существовать метод, обеспечивающш! его чтение или обновление. Если пользователь пишет фрагмент вида:
$him = Person->new();
$him->{NAME} = "Sylvester";
$him->{AGE} = 23;

он нарушает интерфейс объекта и напрашивается па неприятности. Для номинально закрытых атрибутов вы просто не создаете методы, позволяющие обращаться к ним.
Интерфейс на базе функций позволяет изменить внутреннее представление, не рискуя нарушить работу программ. Он позволяет выполнять любые проверки диапазона, а также выполнять необходимое форматирование или преобразование данных. Продемонстрируем сказанное на примере улучшенной версии метода name:
use Carp;
sub name {
my $self = shift;
return $self->{NAME} unless @_;
local $_ = shift;
croak "too many arguments" if @_;
if ($"W) {
/["\s\w'-]/ && carp "funny characters in name'
/\d/ && carp "numbers in name";
/\S+(\s+\S+)+/ || carp "prefer multiword name";
/\S/ || carp "name is blank";
}
s/(\w+)/\u\L$1/g; # Начинать с символа верхнего регистра
$self->{NAME} = $_;
}

Если пользователи (или даже другие классы посредством наследования) обращаются к полю "NAME" напрямую, вы уже несможете добавить подобный код. Настаивая на косвенном обращении ко всем атрибутам данных через функции, вы оставляете за собой свободу выбора.
Программисты, которым приходилось работать с объектами C++, привыкли к тому, что к атрибутам объекта можно обращаться из методов в виде простых переменных. Модуль Alias с CPAN обеспечивает эту и многие другие возможности - например, создание открытых методов, которые могут вызываться объектом, но недоступны для кода за его пределами. Рассмотрим пример создания класса Person с применением модуля Alias. Обновление "магических" переменных экземпляра автоматически обновляет поля данных в хэше. Удобно, правда? package Person;
# То же, что и раньше... sub new {
my $that = shift;
my $class = ref($that) || $that;
my $self = {
NAME => undef, AGE => undef, PEERS => [],
;
bless($self, $class);
return $self;
}
use Alias qw(attr);
use vars qw($NAME $AGE $PEERS);
sub name {
my $self = attr shift;
if (@_) { $NAME = shift; } return $NAME;
}
sub age {
my $self = attr shift;
if (@_) { SAGE = shift; } return SAGE;
}
sub peers{
my $self = attr shift;
if (@_) { OPEERS = @_; } return OPEERS;
}
sub exclaim {
my $self = attr shift;
return sprintf "Hi, I'm %s, age %d, working with %s'
$NAME, SAGE, join(", ", cPEERS);
}
sub happy_birthday {
my $self = attr shift;
return ++$AGE;
}

Директива use vars понадобилась из-за того, что Alias играет с пакетными глобальными переменными, имена которых совпадают с именами полей. Чтобы использовать глобальные переменные при действующей директиве use strict, необходимо заранее объявить их. Эти переменные локализуются в блоке, содержащем вызов attr(), словно они объявлены с ключевым словом local. Таким образом, они остаются глобальными пакетными переменными с временными значениями.

> Смотри также --------------------------------
perltoot(1),perlobj(1) и perlbot(1); документация по модулю Alias с СРАМ; рецепты 13.11-13.12.

13.4. Управление данными класса

Проблема

Вам нужен метод, который вызывается для класса в целом, а не для отдельного объекта. Например, он может обрабатывать глобальный атрибут данных, общий для всех экземпляров класса.

Решение

Первым аргументом метода класса является не ссылка, как в методах объектон.:; строка, содержащая имя класса. Методы классов работают с данными пакета, а иг данными объекта, как показывает приведенный ниже метод 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.

Комментарий

Обычно каждый объект обладает определенным состоянием, полная информация о котором хранится в самом объекте. Значение атрибута данных одного объекта никак не связано со значением этого атрибута в другом экземпляре того же класса. Например, присваивание атрибуту gender объекта her никак не влияет на атрибут gender объекта him, поскольку это разные объекты с разным состоянием:
$him = Person->new();
$him->gender("male");
$her = Person->new();
$her->gender("female");

Представьте атрибут, общий для всего класса - изменение атрибута для одного экземпляра приводит к его изменению для остальных экземпляров. Подобно тому, как имена глобальных переменных часто записываются с большой буквы, некоторые программисты предпочитают записывать имя символами верхнего регистра, если метод работает с данными класса, а не с данными экземпляра. Рассмотрим пример использования метода класса с именем Max_Bounds:
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.

13.5. Использование класса как структуры

Проблема

Вы привыкли работать со структурированными типами данных - более сложными, чем массивы и хэши 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);


Назад
Вперед