Язык программирования Perl

Способы хранения атрибутов


Анонимные хэши - это самый распространенный, но не единственный способ хранить значения атрибутов объекта. Для этого может применяться массив или даже скалярная переменная, лишь бы при создании объекта в конструкторе это хранилище значений было связано с именем класса функцией bless(). Недостатком этого подхода можно считать то, что ограничение доступа к свойствам достигается лишь на уровне соглашения пользоваться только методами объекта. И поскольку существует возможность изменить значение атрибута напрямую, это может нарушить корректную работу программы. Ведь в методе изменение состояния объекта сопровождается необходимыми проверками, чего не происходит при непосредственном изменении атрибута. Тем более, что в некоторых случаях атрибуты вообще должны быть доступны только для чтения (read-only attribute). Например, при использовании хэша для хранения атрибутов вполне возможно такое некорректное присваивание:

$hobbit->{magic} = 'пёстрая'; # добавлен ошибочный атрибут

Для того чтобы надежно обеспечить ограничение доступа к данным, которые хранятся в объекте, применяются замыкания. Чтобы показать, как можно организовать полностью закрытые атрибуты (private attributes) с помощью замыканий, напишем класс Private::Person. В новой версии класса значения атрибутов также хранятся в анонимном хэше, но при создании объекта возвращается ссылка не на него, а на анонимную подпрограмму доступа к данным. Этой функции будет передаваться имя атрибута (и, возможно, новое значение), а она будет возвращать значение атрибута, используя имя атрибута как ключ поиска в анонимном массиве. Это выглядит так:

package Private::Person; # класс "Личность"

sub new { # прототипом может быть my $invocant = shift; # класс или объект my $class = ref($invocant) || $invocant; my $self = { # значения атрибутов: NAME => '', # имя и HEIGHT => 0.0 # рост }; my $closure = sub { # функция доступа к данным my $field = shift; # по имени атрибута $self->{$field} = shift if @_; # изменим и return $self->{$field}; # вернем значение }; # объектом будет bless($closure, $class); # ссылка на функцию }


# метод доступа к атрибуту name sub name { my $self = shift; # ссылка на объект-функцию &{$self}("NAME", @_); # доступ к скрытому значению }

# метод доступа к атрибуту height sub height { # то же, что выше, но несколько короче: &{ $_[0] }("HEIGHT", @_[1 .. $#_ ] ) } 1;

Методы доступа к свойствам объектов получают первым аргументом ссылку на функцию доступа к значениям атрибутов и вызывают ее, передавая ей имя поля и остальные свои аргументы. Приведем пример создания объектов нового класса и обращения к их методам. В вызывающей программе все выглядит так, как будто данные по-прежнему хранятся в анонимном массиве:

package main; # вызывающая программа use Private::Person; # использовать этот класс

my $elf = Private::Person->new; # создать объект и $elf->name("Леголас"); # задать значения $elf->height(189); # его атрибутам # получить доступ к значениям атрибутов объекта print $elf->name, ' ', $elf->height, ' '; print ref($elf), "\n"; # тип референта: 'Private::Person'

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

Обратите также внимание на то, что конструктор класса Private::Person определен так, что он может вызываться с использованием либо имени класса, либо ссылки на существующий объект. Это проверяется в следующей строке:

my $class = ref($invocant) || $invocant;

Если первым аргументом передана ссылка на объект, то определяется имя его класса, иначе считается, что передано имя класса. Поэтому в программе можно создавать однотипные объекты, обращаясь к методу new() существующего объекта. Например, так:

my $hobbit = Private::Person->new; # вызов с именем класса $hobbit->name("Bilbo Baggins");

my $frodo = $hobbit->new; # вызов со ссылкой на объект $frodo->name("Frodo Baggins");


Содержание раздела