package Wizard; # класс "Маг"
our @ISA = qw(Person); # является подклассом Person
use Person; # и использует пакет Person
# ... описание методов класса Wizard...
1; # вернуть истину для use
Смысл наследования - в создании подклассов, изменяющих поведение базового класса. Для этого в дочерних классах описываются новые методы или переопределяются существующие. В качестве примера опишем для класса Wizard новый метод для работы со свойством 'magic' ("тип магии" - белая или черная):
sub magic { # магия - вот что отличает волшебника
my $self = shift; # извлечь ссылку на объект
$self->{magic} = shift if @_; # изменить значение
return $self->{magic}; # вернуть значение
}
Кроме того, переопределим конструктор объектов класса new() так, чтобы он принимал два аргумента для инициализации свойств 'name' и 'magic'. Для создания объекта воспользуемся конструктором родительского класса, затем зададим начальные значения свойств, и, наконец, "дадим благословение" объекту ссылки быть магом:
sub new { # конструктор объектов
my $class = $_[0]; # имя класса в 1-м аргументе
my $self = new Person; # маг - это личность
$self->{name} = $_[1]; # задать имя из 2-го аргумента
$self->{magic} = $_[2]; # и тип магии из 3-го
bless($self, $class); # "благословить" мага
return $self; # вернуть ссылку на объект
}
Вызывающая программа, использующая производный класс, будет выглядеть следующим образом:
use Wizard; # подключить производный класс
# создать нового черного мага - Сарумана
my $wizard = new Wizard('Саруман', 'black');
my $name = say_name $wizard; # "назови себя, маг"
print $name, ' ', $wizard->magic(); # 'Саруман black'
print ref($wizard); # тип объекта ссылки - 'Wizard'
Естественно, что у объекта класса Wizard можно вызывать не только методы собственного класса, но и любые методы, унаследованные из родительского класса Person.
В классе может быть описан специальный метод, автоматически вызываемый исполняющей системой при уничтожении каждого объекта. Такой метод называется деструктор (destructor), и он должен иметь зарезервированное имя - DESTROY. Деструктор вызывается при освобождении памяти, занимаемой объектом: это происходит при выходе из блока, где был создан объект, при удалении последней ссылки на объект функцией undef($object) или при окончании программы. Приведем пример шуточного деструктора для класса Person, который при удалении объекта направляет прощание в поток STDERR, называя имя объекта методом say_name():
sub DESTROY {
warn('Прощайте, я ухожу... ' . shift->say_name);
}
Деструктор может использоваться, если при окончании работы с объектом нужно выполнить какие-то завершающие действия: например, удалить динамически созданные структуры или сохранить данные объекта в файле. Конструктор в этом случае может считывать сохраненные значения из файла, чтобы присвоить объектам начальные значения.
Анонимные хэши - это самый распространенный, но не единственный способ хранить значения атрибутов объекта. Для этого может применяться массив или даже скалярная переменная, лишь бы при создании объекта в конструкторе это хранилище значений было связано с именем класса функцией bless(). Недостатком этого подхода можно считать то, что ограничение доступа к свойствам достигается лишь на уровне соглашения пользоваться только методами объекта. И поскольку существует возможность изменить значение атрибута напрямую, это может нарушить корректную работу программы. Ведь в методе изменение состояния объекта сопровождается необходимыми проверками, чего не происходит при непосредственном изменении атрибута. Тем более, что в некоторых случаях атрибуты вообще должны быть доступны только для чтения (read-only attribute). Например, при использовании хэша для хранения атрибутов вполне возможно такое некорректное присваивание:
$hobbit->{magic} = 'пёстрая'; # добавлен ошибочный атрибут
Для того чтобы надежно обеспечить ограничение доступа к данным, которые хранятся в объекте, применяются замыкания. Чтобы показать, как можно организовать полностью закрытые атрибуты (private attributes) с помощью замыканий, напишем класс Private::Person. В новой версии класса значения атрибутов также хранятся в анонимном хэше, но при создании объекта возвращается ссылка не на него, а на анонимную подпрограмму доступа к данным. Этой функции будет передаваться имя атрибута (и, возможно, новое значение), а она будет возвращать значение атрибута, используя имя атрибута как ключ поиска в анонимном массиве. Это выглядит так: