Глава 16. Создание классов и объектов

Коротко

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

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

  • Класс — это пакет, который может обеспечивать методы.

  • Метод — это подпрограмма, встроенная в класс или объект. Метод использует ссылку на объект или имя класса, передаваемое ему в качестве первого аргумента.

  • Объект — это ссылка на элемент данных. В отличие от других ссылок, этот элемент знает, к какому классу он относится. Объекты создаются через классы.

  • Наследование — это процесс порождения одного класса (дочернего, или порожденного) из другого класса (базового, или родительского). В результате этого процесса дочерний класс может использовать методы родительского класса.

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

    Классы

    В Perl класс — это просто пакет, обеспечивающий методы для других частей программы. (Метод — это подпрограмма, связанная с объектом или классом.) В объектно-ориентированном программировании (ООП) классы представляют собой нечто типа шаблонов для объектов. То есть, если представить себе класс как форму для кондитерских изделий, то объекты, создаваемые на его основе — это и есть выпеченные в ней печенья. Можете рассматривать класс как тип данных объекта (насколько эта аналогия может работать в таком языке программирования со слабой концепцией типа данных, каким является Perl). Класс используется для создания объектов, а объекты вызывать методы, принадлежащие классу.

    Объект создается вызвовом конструктора класса. Обычно это метод класса new. Конструктор возвращает ссылку на новый объект или класс. Если обратиться к внутренним деталям, то конструктор использует функцию bless для установления связи между ссылкой (обычно — ссылкой на данные внутри нового объекта) и классом, тем самым создавая объект. (Помните, что объект — это просто ссылка на элемент данных, который знает, к какому классу он относится.)

    Рассмотрим пример класса Class1, для которого определен конструктор new. В этом конструкторе создается ссылка на анонимный хеш, который будет хранить данные объекта. (Естественно, необязательно использовать для хранения данных хеш — в зависимости от ситуации, подойдет массив или даже скаляр.) После создания объект связывается с текущим классом и, наконец, конструктор возвращает ссылку на объект:

    package Class1;

    sub new

    {

       my $self = {};

       bless($self);

       return $self;

    }

    return 1;

    Именно так организованы классы Perl. Возникает вопрос: как создать объект этого класса? К этому вопросу мы перейдем в следующем разделе.

    Объекты

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

    Для создания объекта вызывается конструктор, который обычно имеет имя new. В следующем примере мы создаем объект определенного раннее класса Class1:

    use Class1;

    my $object = Class1->new();

    Этот объект, однако, не очень полезен, потому что он не хранит никаких данных и не поддерживает никаких методов, — но это только пока.

    Методы

    Если у вас есть объект, который поддерживает методы (как научить его поддерживать методы мы, правда, еще не знаем), то вы можете их использовать. В следуеющем примере метод calculate вызывается с параметрами $operand1 и $operand2, а результат вычислений сохраняется в переменной $result:

    $result = $object1->calculate($operand1, $operand2);

    В Perl есть два типа методов — методы класса и методы объекта (методы экземпляра). Последние подобны приведенному выше calculate, — они вызываются через объект и передают ссылку на него в качестве первого параметра. Методы класса вызываются через указание класса, и передают в качестве первого параметра имя класса. Например, методом класса является определенный в предыдущем разделе конструктор new.

    my $object1 = Class1->new();

    Вы можете сохранять в объектах элементы данных и извлекать данные из объектов. Например, вот как сохранить, а затем извлечь некоторое данное из объекта $object1, приписав ему ключ DATA в хеше:

    $object1->{DATA} = 1025;

    my $data = $object1->{DATA};

    Однако способ, на котором основано объектно-ориентированное программирование, и с помощью которого обычно работают объекты Perl, заключается в том, что данные скрываются за методами доступа. Это означает, что вместо того, чтобы извлекать или присваивать данные напрямую, используется, скажем, метод getdata для чтения и setdata — для присвоения данных:

    $object1->setdata(1024);

    my $data = $object1->getdata();

    Методы доступа позволяют контролировать доступ к данным объекта, — например, другие части программы не смогут присвоить данные, еслди это запрещено. Тем самым создается интерфейс (набор методов), с помощью которого объект взаимодействует с остальной частью программы.

    Хотя методы — это подпрограммы пакета (откроем эту маленькую тайну), их не следует экспортировать для доступа в другой части программы. Лучше ссылаться на них через имя класса или объекта. Это тесно связано с еще одной важной концепцией объектно-ориентированного программирования, которую мы и должны рассмотреть перед тем, как перейти к кодированию, — а именно, с наследованием.

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

    С помощью наследования на основе существующего класса порождается новый. Он будет наследовать методы и элементы данных старого, если только они не будут замещены новыми. Кроме того, для расширения функциональности нового класса, к нему можно добавить все, что пожелаете. Например, если у вас есть класс vehicle (средство передвижения), можно создать на его основе новый класс car (автомобиль), и добавить к нему метод horn (гудок), который будет выводит слово «beep». В этом случае новый класс создается из базового, и «наращивается» новым (дополнительным) методом. С практической реализацией механизма наследования мы познакомимся далее в этой главе.

    Итак, теперь, когда мы вчерне познакомились с концепциями объектно-ориентированного программирования, настало время перейти к разделу «Непосредственные решения».

    Непосредственные решения

    Создание класса

    Как создать в Perl класс? Для этого достаточно создать пакет (тем самым вы создаете класс):

    package Class1;

    return 1

    И это все, что нужно, чтобы называться классом. Удивлены? Не удивляйтесь: в Perl класс — это всего лишь пакет (точнее, в Perl классы эмулируются с помощью пакетов). Обычно, однако, классы имеют встроенные методы, в том числе один очень важный, — конструктор, позволяющий создавать новые объекты класса. (Детали относительно конструкторов приводятся в следующем разделе.)

    Создание конструктора

    В Perl конструкторы — это просто методы с именем new, возвращающие ссылку на связанный с классом объект. Вот пример конструктора, который создает ссылку на анонимный хеш (в котором хранятся данные), привязывает ее к текущему классу и возвращает как ссылку на новый объект:

    package Class1;

    sub new

    {

       my $self = {};

       bless($self);

       return $self;

    }

    return 1;

    Как заставить конструктор работать, чтобы создать с его помощью новый объект? Об этом — в следующем разделе.

    Подсказка 1: Функция bless не просто связывает ссылку с пакетом (классом), но и возвращает ее в качестве результата. Поэтому две последние строчки конструктора можно заменить одной командой: «return bless $self;».

    Подсказка 2: По умолчанию функция bless связывает ссылку с текущим пакетом (классом). В одной из форм вызова bless получает два параметра, — вторым является имя класса, с которым связывается ссылка. Как это использовать, рассказывается в разделе «Наследование конструкторов» далее в этой главе.

    Создание объекта

    Чтобы создать на основе класса новый объект, вызывается конструктор класса, который возвращает ссылку на новый объект:

    package Class1;

    sub new

    {

       my $self = {};

       bless($self);

       return $self;

    }

    return 1;

    use Class1

    my $object1 = Class1->new();

    Таким образом, мы создали новый объект. Однако объекты без методов не слишком полезны, если только они не поддерживают методы. О методах — смотрите следующий раздел.

    Создание метода класса

    Есть два типа методов: методы класса и методы объекта (методы экземпляров класса). Метод класса вызывается с помощью имени класса, а метод экземпляра класса — с помощью объекта (ссылки). В качестве первого параметра метод класса получает имя класса. Конструкторы — типичные представители методов класса. Вот пример метода класса, который выводит сообщение и возвращает имя класса:

    package Class1;

    sub new

    {

       my $self = {};

       bless($self);

       return $self;

    }

    sub name

    {

       my $class = shift;

       print "This is the class method.\n";

       return $class;

    }

    return 1;

    А вот как мы используем этот метод:

    use Class1

    $classname = Class1->name();

    print "This method belongs to class '$class'.\n";

    This is the class method.

    This method belongs to class 'Class1'.

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

    use Class1

    $classname = Class1::name("Class1");

    print "This method belongs to class '$class'.\n";

    This is the class method.

    This method belongs to class 'Class1'.

    Создание метода экземпляра

    При использовании метода объекта (то есть экземпляра класса) ссылка на объект передается методу в качестве первого параметра. Она позволяет добраться до данных, скрытых внутри объекта, и до методов объекта. В следующем примере создается метод объекта data. Первый аргумент, передаваемый методу — это ссылка на сам объект. Мы используем ее, чтобы сохранить данные в анонимном хеше (если пользователь передал данные), а затем возвращаем присвоенные данные:

    package Class1;

    sub new

    {

       my $self = {};

       bless($self);

       return $self;

    }

    sub data

    {

       my $self = shift;

       if (@_) {$self->{DATA} = shift;}

       return $self->{DATA};

    }

    return 1;

    Вызов этого метода имеет вид:

    use Class1

    my $object1 = Class1->new();

    $object1->data("Hello!");

    print "Here is the text in the object: ", 

          $object1->data();

    Here is the text in the object: Hello! 

    Вызов метода

    Вы можете вызывать методы Perl двумя способами. С первым мы уже знакомы — это оператор-стрелка -> и имя класса или объекта в качестве префикса. Вот пример такого вызова (метод data инициализирует объект значением ноль):

    package Class1;

    sub new

    {

       my $self = {};

       bless($self);

       $self->data(0);

       return $self;

    }

    sub data

    {

       my $self = shift;

       if (@_) {$self->{DATA} = shift;}

       return $self->{DATA};

    }

    return 1;

    Тот результат мы получим, вызвав метод как подпрограмму (то есть, используя стандартный синтаксис Perl). Обратите внимание, что в качестве первого параметра необходимо передать ссылку на объект:

    sub new

    {

       my $self = {};

       bless($self);

       data($self, 0);

       return $self;

    }

    Создание переменной экземпляра

    Данные, сохраняемые в объекте, называются данными экземпляра, а переменные, используемые для хранения данных,  — переменными экземпляра. Один из способов создания переменных экземпляра — создание объекта в виде анонимного хеша и запись данных с помощью ключа, интерпретируемого как имя переменной экземпляра. (Также можно использоватьмассивы, или, что более редко, скаляры.) Например, вот так сохраняется имя клиента (переданное как параметр при вызове конструктора), с помощью ключа хеша NAME:

    package Class1;

    sub new

    {

       my $self = {};

       shift;       # удалить из параметров списка имя класса

       if (@_) {

          $self->{NAME} = shift;

       } else {

          $self->{NAME} = "Anonymous";

       }

       bless($self);

       return $self;

    }

    return 1;

    Теперь при создании объекта, принадлежащего этому классу, можно использовать данное, имеющееся внутри объекта:

    use Class1

    my $object1 = Class1->new("Christine");

    print "The person's name is ", $object1->{NAME}, "\n";

    The person's name is Christine

    Однако, как уже было сказано в начале главы, правильная стратегия состоит в том, чтобы скрывать данные за методами доступа. Значит, вместо того, чтобы извлекать данные напрямую, лучше создать метод getdata, возвращающий текущее данное и использующий его при выводе имени клиента.

    Создание приватных методов и данных

    Хотя многие объектно-ориентированные языки программирования поддерживают приватные методы и переменные, (то есть, внутренние методы и переменные, недостижимые вне класса или объекта), Perl не позволяет делать этого явно.

    Подсказка: Вы всегда можете использовать переменные с лексической областью видимости (ключевое слово my), чтобы ограничить область видимости переменных текущим пакетом.

    Чтобы создать приватные переменные, надо воспользоваться следующим соглашением Perl: приватные имена начинаются с символа подчеркивания. В отличие от языков типа C++, Java или Delphi, это не значит, что вы не можете получить доступ к приватным переменным и методам объекта. Идея состоит в том, что если имя начинается с символа подчеркивания, его не следует использовать, поскольку оно считается приватным. В следующем примере публичный (открытый) метод sum использует приватный (закрытый) метод _add для сложения двух величин:

    package Class1;

    sub new

    {

       my $type = {};

       $type->{OPERAND1} = 2;

       $type->{OPERAND2} = 2;

       bless($self);

       return $self;

    }

    sub sum

    {

       my $self = shift;

       my $temp = _add ($self->{OPERAND1}, 

                        $self->{OPERAND2}); 

       return $temp;

    }

    sub _add {return shift() + shift();}

    return 1;

    Вот результат использования метода sum:

    use Class1

    my $object1 = Class1->new();

    print "Here is the sum: ", $object1->sum;

    Here is the sum: 4

    Создание переменной класса

    Вы уже видели, как создаются переменные экземпляра для хранения данных в объекте. Однако данные можно также хранить в переменных класса. Переменной с лексической областью видимости, объявленная как глобальная, доступна всем объектам класса. В следующем примере, мы отслеживаем полное число объектов, созданных для конкретного класса, запоминая его в переменной класса $total (то есть, эта переменная будет хранить общее значение для всех объектов данного класса). При создании каждого нового объекта значение переменной $total увеличивается:

    package Cdata;

    my $total = 0;

    sub new

    {

       $self = {};

       $total++;

       return bless $self;

    }

    sub gettotal {return $total;}

    return 1;

    Обратите внимание, что мы добавили метод gettotal, возвращающий значение переменной $total:

    use Cdata;

    $object1 = Cdata->new;

    print "Current number of objects: ",

          $object1->gettotal, "\n";

    $object2 = Cdata->new;

    print "Current number of objects: ",

          $object2->gettotal, "\n";

    $object3 = Cdata->new;

    print "Current number of objects: ",

          $object3->gettotal, "\n";

    Current number of objects: 1

    Current number of objects: 2

    Current number of objects: 3

    Как видите, данные класса координируют поведение всех объектов класса, что делает их полезными для хранения счетчиков, данных для инициализации — то есть, данных, выходящих за границы объектов.

    Создание деструктора

    При создании объектов используются конструкторы. Для выполнения команд Perl, связанных с уничтожением объектов, можно использовать деструкторы, — точнее, они начинают работать, когда объект готов к уничтожению. (В Perl работает автоматическая система сборки мусора. Она уничтожает элементы и освобождает занимаемую ими память, когда они больше не нужны, — например, когда потеряна ссылка на данные, переменная выходит за границы области видимости, либо интерпретатор прекращает работу.) В отличие от конструкторов, для деструкторов Perl имеется вполне определенное имя DESTROY:

    Подсказка: Обратите внимание, что подобно остальным неявно вызываемым функциям, DESTROY имеет имя, составленное из заглавных букв. Предполагается, что DESTROY вызывается Perl и никогда не вызывается пользователем.

    В следующем примере определяется деструктор, который выводит сообщение об уничтожении очередного объекта:

    package Class1;

    sub new

    {

       my $type = {};

       bless($self);

       return $self;

    }

    sub DESTROY

    {

       print "Object is being destroyed!\n";

    }

    return 1;

    Теперь при уничтожении очередного объекта (в результате работы системы автоматической сборки мусора), на экране появляется сообщение:

    use Class1;

    $object1 = Class1->new();

    $object2 = Class1->new();

    $object1 = 0;

    print "» x 30;

    Object is being destroyed!

    ------------------------------

    Object is being destroyed!

    (Первое сообщение появляется потому, что скаляр $object1 потерял ссылку на объект, и объект был уничтожен. Второе появилось потому, что интерпретатор прекратил работу и все оставшиеся «в живых» объекты — а именно, объект $object2,  — были уничтожены.)

    Как реализовать наследование классов

    Один из наиболее важных аспектов объектно-ориентированного программирования — это наследование. Оно позволяет создавать библиотеки классов и настраивать классы, сохраняя все встроенные в них возможности.

    Как уже говорилось в начале главы, класс, который мы называем порожденным (или дочерним), может наследовать переменные и методы другого класса, называемого базовым (или родительским). Дочерний класс имеет доступ ко всем переменным и методам базового (в отличие от других объектно-ориентированных языков программирования, в Perl нельзя объявить члены класса как private или как protected). В следующем примере от базового класса Class1 порождается дочерний Class2. Обратите особое внимание, что Class1 имеет метод gettext, который мы будем использовать потом для класса Class2. Итак, вот исходный текст Class1:

    package Class1;

    sub new

    {

       my $self = {};

       bless($self);

       return $self;

    }

    sub gettext {return "Hello!\n";}

    return 1;

    А вот класс Class2, наследующий от Class1 путем подключения этого класса с помощью команды use Class1 и включение его имени Class1 в список (массив) @ISA. (Для тех, кто знаком с объектно-ориентированным программированием в C++ и Dephi, это имя должно напоминать условие «is a», связывающее два класса в случае наследования: Class2 is a Class1.) Итак, исходный текст для класса Class2:

    package Class2;

    use Class1;

    @ISA = qw/Class1/;

    sub new

    {

       my $self = {};

       bless($self);

       return $self;

    }

    return 1;

    Если Perl не может найти какого-либо метода или переменной в указанном каком-либо классе, он проверяет классы, заданные в массиве @ISA, в порядке следования (если, конечно, такой массив существует). Тем самым с помощью массива @ISA в Perl реализуется наследование классов. Предположим, для первого класса в его списке @ISA не будет найдено требуемого метода. При этом у этого класса есть свой список @ISA. Тогда Perl сначала проверит полное дерево наследования первого класса и только затем перейдет к поиску недостающего метода или переменной во втором классе, указанном в массиве @ISA исходного класса. Тем самым в Perl реализуется алгоритм поиска «сперва вглубь, потом вширь», решающий проблему множественного наследования.

    Подсказка: Как легко заметить, Perl не проверяет структуру данных, создаваемую в конструкторе данного класса, даже на минимальную совместимость с методами, которые наследуются от родительских классов. Совместимость данных и методов — целиком на совести программиста. Это лишь один из примеров того, как слабо поддерживаются концепции объектно-ориентированного программирования в Perl и насколько много приходится делать программисту «руками», чтобы все тем не менее работало, как положено.

    Проверим, как работает наследование в случае класса Class2:

    use Class2;

    my $object1 = Class2->new();

    print "The object says: ", $object1->gettext;

    The object says: Hello!

    Здесь мы создали объект класса Class2 и использовали метод gettext из класса Class1, — и это сработало! Иными словами, Class2 унаследовал метод gettext от Class1.

    Подсказка: Обратите внимание, что конструктор класса Class2 сначала вызывает конструктор класса Class1. Это общепринятая практика, позволяющая наследовать не только методы, но и поля данных, инициализируемых в конструкторе базового класса.

    Если вы внимательны, то заметили кое-что важное в этом примере, — а именно, конструктор для класса Class2 сначала вызывает конструктор для класса Class1, чтобы получить объект, «обладающий» методом gettext, а затем возвращает его как объект класса Class2, как сделал бы любой нормальный конструктор. Однако при этом возникает проблема, — ведь конструктор класса Class1 возвращает объект класса Class1, а не Class2! В результате созданный объект является экземпляром Class1, а не Class2, не так ли? Эта проблема решается повторным вызовом функции bless, которая связывает объект и класс Class2. Кроме того, мы могли бы дополнительно инициализировать поля данных, отсутствующие в классе Class1, и внести любые другие исправления, характерные для объектов класса Class2.

    Однако в исходном виде конструктор класса Class2 слишком напоминает конструктор класса Class1, чтобы мы не попытались «унаследовать» его. Теперь, зная, как работает в Perl механизм наследования, мы можем переписать конструктор класса Class1 так, чтобы он возвращал объекты класса Class2, когда это нам нужно. Мы займемся этим в следующем разделе.

    Наследование конструкторов

    В примере, рассмотренном в предыдущем разделе, конструктор класса Class2 можно унаследовать из класса Class1 (поскольку код Perl для этих конструкторов совпадает), но так как конструктор класса Class1 и возвращает объект класса Class1, а не класса Class2, это вызвало бы проблемы. Чтобы конструктор класса Class1 возвращал объекты класса Class2 (или любого другого, для которого Class1 является базовым), при работе с ним в контексте дочернего класса, его необходимо переписать. Для этой цели мы используем специальную форму функции bless с двумя аргументами, с которой мы еще не сталкивались.

    Второй аргумент bless задан должен задавать имя класса, с которым будет связан объект. (Отметим, что это свойство функции может привести к неприятностям при неосторожном обращении, так как можно связать любую структуру данных с любым классом без какой-либо проверки Perl совместимости подобного связывания.) В нашем случае bless получает в качестве второго параметра имя базового или дочернего класса в зависимости от того, для создания какого объекта он используется. То есть, если мы вызываем этот вариант конструктора класса как Class2->new(), то он возвратит нам объект класса Class2:

    class Class1;

    sub new

    {

       my $class = shift;

       my $self = {};

       bless ($self, $class);

       return $self;

    }

    return 1;

    (Мы использовали здесь тот факт, что имя класса передается конструктору в качестве первого параметра, поскольку в этом отношении конструктор ничем не отличается от любого другого метода класса. В результате все, что нам пришлось сделать — это извлечь из списка параметров правильное имя класса (первый аргумент) и передать его функции bless.)

    То же самое будет происходить и для всех дочерних классов Class1. То есть, если мы вызываем метод new Class1 как Класс->new(), то он возвратит нам объект класса Класс. Тем самым получаем, что конструктор Class1 действительно наследуется дочерними классами. (Отметим, что в данном случае использование функции bless с двумя аргументами не будет приводить к неприятностям: добраться до метода Class1::new, вызывая его в форме Класс->new(), мы можем только в том случае, если Класс на самом деле наследует от Class1.)

    Вы можете не только заставить конструктор создавать объекты дочерних классов, но и организовать его таким образом, чтобы его можно было вызывать и как метод класса, и как метод объекта. Как это сделать, показано в следующем примере:

    package Class1;

    sub new

    {

       my $this = shift;

       $class = ref($this) || $this;

       my $self = {};

       bless $self, $class;

       return $self;

    }

    return 1;

    Здесь использован тот факт, что встроенная функция ref возвращает значение ложь, если ее аргумент не является ссылкой, и тип данных (в виде текстовой строки), на который мы ссылаемся, в противном случае. В частности, для ссылок, связанных с классом (то есть превращенных в объект), ref возвращает имя класса. Поэтому если конструктор вызван как метод объекта (первый параметр — это объект), то имя класса вычисляется функцией ref. Если же конструктор вызван как метод класса, то первый параметр — это имя класса (текстовая строка, а не ссылка), и имя класса вычисляется как содержимое переменной $this, поскольку функция ref возвращает значение ложь.

    Подсказка: Очевидно, что тот же прием можно использовать и для обычных методов класса, если мы хотим работать с ними, как с методами объекта, и как методы класса, варьируя поведение метода в зависимости от формы вызова.

    Следующее «усовершенствование» схемы, которой мы можем руководствоваться при создании конструктора, можно найти в разделе «Наследование методов: методы инициализации» далее в этой главе.

    Наследование переменных экземпляра

    Кроме методов можно наследовать данные базового класса, порождая один класс на основе другого. Perl рекомендует сохранять переменные экземпляра внутри хеша базового класса (если в качестве базового объекта выбран хеш, к нему легко добавить новые поля данных):

    package Class1;

    sub new

    {

       my $class = shift;

       my $self = {};

       $self->{NAME} = "Christine";

       bless $self, $class;

       return $self;

    }

    return 1;

    Использование массива вместо хеша менее удобно, так как производные классы могут не разобраться, какие индексы предназначены для какого класса. Гораздо легче разделить данные с помощью системы ключей, имеющих содержательные имена. Например, когда вы создавая на основе Class1 новый класс Class2, можно добавить новые данные, просто сохранив их под другим ключом:

    package Class2;

    use Class1;

    @ISA = qw/Class1/;

    sub new

    {

       my $class = shift;

       my $self = {};

       $self->{DATA} = 200;

       bless $self, $class;

       return $self;

    }

    return 1;

    Теперь мы можем использовать данные, имеющиеся в текущем экземпляре класса Class2, причем этот экземпляр наследует часть данных от класса Class1:

    use Class2;

    my $object1 = Class2->new();

    print $object1->{NAME}, " has \$",

          $object1->{DATA}, "\n";

    Christine has $200

    Точно так же в конструкторе класса Class2 можно изменить содержание поля NAME, тем самым заместив прежнее поле данных новым (например, ссылкой на массив, состоящий из списка имен). (Обратите внимание, что перед инициализацией полей данных внутри конструктора класса Class2, мы вызвали конструктор класса Class1 — именно эта процедура позволяет наследовать от базового класса его поля данных.)

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

    Как уже отмечалось в разделе «Как реализовать наследование классов», благодаря массиву @ISA и команде use дочерний класс может наследовать от базового методы (включая конструкторы, если они организованы специальным образом — см. раздел «Наследование конструкторов»). Тем самым вы можете использовать, существующие в базовом классе методы, а также добавлять новые:

    package Class1;    # базовый класс

    sub new

    {

       my $class = shift;

       my $self = {};

       bless $self, $class;

       return $self;

    }

    sub printB {return "Bye...";}

    return 1;

    package Class2;

    use Class1;

    @ISA = qw/Class1/;

    # конструктор наследуется, поэтому метода `new’ нет

    sub printH {return "Hello!";}

    return 1;

    use Class2;

    $object1 = Class2->new();      # конструктор наследуется

    print $object1->printH, "\n";  # добавленный метод

    print $object1->printB, "\n";  # метод наследуется

    Hello!

    Bye...

    Однако, помимо использования уже имеющихся методов и добавления новых, в распоряжении пользователя имеется еще одна возможность — замещение методов базового класса:

    package Class2;

    use Class1;

    @ISA = qw/Class1/;

    # конструктор наследуется, поэтому метода `new’ нет

    sub printH {return "Hello!";}

    sub printB {return "Ciao!";}

    return 1;

    use Class2;

    $object1 = Class2->new();      # конструктор наследуется

    print $object1->printH, "\n";  # добавленный метод

    print $object1->printB, "\n";  # замещенный метод 

    Hello!

    Ciao!

    Однако, если вы не переписываете старый метод, а лишь корректируете его работу или добавляете новые операции (особенно часто такое поведение требуется для конструкторов), может понадобиться и исходный вариант метода. Если вызывается класса, то проблем не возникает: просто указывается имя базового класса, как это сделано в следующем примере:

    package Class2;

    use Class1;

    @ISA = qw/Class1/;

    # конструктор наследуется, поэтому метода `new’ нет

    sub printH {return "Hello!";}

    sub printB

    {

       my $temp = Class1->printB;

       return $temp . " by now!";

    }

    return 1;

    use Class2;

    print Class2->printH, "\n";  # добавленный метод

    print Class1->printB, "\n";  # исходный метод 

    print Class2->printB, "\n";  # замещенный метод 

    Hello!

    Bye...

    Bye... by now!

    Но если используется метод объекта, возникают трудности, — для вызова родительского метода нельзя создать объект родительского класса из дочернего. Точнее, можно вызвать функцию bless, указав в качестве второго параметра имя родительского класса и затем восстановить его исходное состояние, указав имя истинного класса в качестве второго параметра. Но это на редкость неуклюжее и потенциально опасное решение, нарушающее логику работы с объектами в объектно-ориентированном подходе:

    package Class2;

    .....

    sub printB

    {

       my $self = shift;

       bless $self, "Class1";

       my $temp = $self->printB;

       bless $self, "Class2";

       return $temp . " by now!";

    }

    return 1;

    $object1 = Class2->new();      # конструктор наследуется

    print $object1->printH, "\n";  # добавленный метод

    print $object1->printB, "\n";  # замещенный метод 

    Hello!

    Bye... by now!

    Более приемлемый вариант — отказаться от вызова метода родительского класса, как метода, и использовать его как обычную подпрограмму:

    package Class2;

    .....

    sub printB

    {

       my $self = shift;

       my $temp = Class1::printB($self);

       return $temp . " by now!";

    }

    return 1;

    use Class2;

    $object1 = Class2->new();      # конструктор наследуется

    print $object1->printH, "\n";  # добавленный метод

    print $object1->printB, "\n";  # замещенный метод 

    Hello!

    Bye... by now!

    Впрочем, Perl предлагает куда более элегантное решение. Чтобы вызвать исходный вариант метода, перед его именем метода ставится префикс SUPER, как в следующем примере:

    package Class2;

    .....

    sub printB

    {

       my $self = shift;

       my $temp = $self->SUPER::printB;

       return $temp . " by now!";

    }

    return 1;

    use Class2;

    $object1 = Class2->new();      # конструктор наследуется

    print $object1->printH, "\n";  # добавленный метод

    print $object1->printB, "\n";  # замещенный метод 

    Hello!

    Bye... by now!

    К сожалению, использовать удвоенный префикс SUPER, чтобы вызвать метод родительского класса родительского класса, не удастся, — он работает ровно на один шаг вверх и не может указываться как SUPER::SUPER::имя. Кроме того, префикс SUPER работает только внутри методов класса, поскольку выполняет поиск метода среди классов, перечисленных в текущем массиве @ISA (поэтому использовать его из любой другой точки не получится).

    Наследование методов: виртуальные методы

    Одной из основополагающих концепций объектно-ориентированного программирования являются виртуальные методы. Предположим, у базового класса есть методы subrA и subrB. Первый работает сам по себе, а subrB вызывает его. Теперь возьмем дочерний класс базового класса, замещающий метод subrA, но наследующий от базового класса метод subrB. Что произойдет при вызове метода  для объекта дочернего класса?

    В большинстве объектно-ориентированных языков программирования ответ на этот вопрос зависит от того, описан ли метод subrA как виртуальный (синтаксис, необходимый для объявления метода виртуальным, может быть самым разным). Если метод subrA — статический («нормальный»), то при вызове унаследованного метода subrB по-прежнему будет вызываться subrA базового класса. Если же метод subrA — виртуальный, то subrA дочернего класса замещает subrA базового, не только при вызове этого метода как из дочернего, так и из порожденных из него классов, но и внутри метода subrB базового класса (естественно, когда он вызывается из дочернего класса и классов, выводимых из дочернего класса). В результате при вызове метода subrB из дочернего класса, он будет вызывать метод subrA дочернего, а не базового класса.

    В Perl вопрос о том, является ли метод виртуальным или нет, решается просто: все методы, вызываемые как методы классов, являются статическими. Наоборот, все методы, вызываемые как методы объектов, являются виртуальными. Тем самым свойство «виртуальности» зависит от способа вызова, и один и тот же метод может быть как виртуальным, так и статическим. Пример:

    package Class1;

    sub new {

       my $class = shift;

       my $self = {};

       bless $self, $class;

       return $self;

    }

    sub gettext{

       return "Hello!\n";

    }

    sub printA

    {

       my $self = shift;

       print $self->gettext;    # виртуальный вызов

    }

    sub printZ

    {

       my $self = shift;

       print Class1->gettext;     # статический вызов

    }

    return 1;

    package Class2;    # конструктор наследуется

    use Class1;

    @ISA = qw/Class1/;

    sub gettext{      # замещение метода

       return "Bye...";

    }

    return 1;

    Теперь проверим, как этот механизм работает:

    use Class1;

    use Class2;

    $object1 = Class1->new();

    $object2 = Class2->new();

    # статический вызов замещенного метода

    $object1->printZ;

    $object2->printZ;

    # виртуальный вызов замещенного метода

    $object1->printA;

    $object2->printA;

    Hello!

    Hello!

    Hello!

    Bye...

    Наследование методов: методы инициализации

    Тот факт, что методы Perl, вызываемые как методы объекта, ведут себя как виртуальные методы традиционных языков программирования, позволяет нам еще больше усовершенствовать схему конструкторов (см. выше раздел «Наследование конструкторов»). А именно, разобьем конструктор на две части: метод new, который выделяет память под объект (анонимный хеш, анонимный массив или, даже, анонимный скаляр) и привязывает ссылку к классу, и init, который инициализирует выделенную память. Тогда мы можем замещать в дочерних классах только инициализирующую часть init, оставляя конструктор неизменным. Рассмотрим пример:

    package Class1;

    sub new

    {

       my $class = shift;

       my $self = {};

       bless $self, $class;

       $self->init(@_);

       return $self;

    }

    sub init

    {

       my $self = shift;

       $self->{DATA} = shift;

       return $self;

    }

    return 1;

    package Class2;    # конструктор new наследуется

    use Class1;

    @ISA = qw/Class1/;

    sub init

    {

       my $self = shift;

       $self->{DATA} = shift;

       $self->{VALUE} = shift;

       return $self;

    }

    return 1;

    use Class1;

    use Class2;

    $object1 = Class1->new(1024);

    $object2 = Class2->new(1024, 3.141592653589);

    Рассмотрим, как создается объект $object1. Конструктор класса Class1 создает ссылку на анонимный хеш, извлекает из списка параметров имя класса ("Class1") и присоединяет ссылку к этому классу. После этого вызывается метод init, которому в качестве параметров передаются параметры конструктора (напомню, что имя класса мы из списка параметров уже удалили). Метод инициализирует поля данных объекта и возвращает инициализированную ссылку-объект в качестве результата. (Это делается затем, чтобы можно было превратить последние две строчки конструктора в одну — то есть записать их в виде return $self->init(@_), — хотя в данном примере мы и не использовали эту возможность.) Конструктор возвращает точке вызова проинициализированный объект. Объект $object1 создан.

    В случае объекта $object2 работа происходит немного другим образом. Так как у класса Class2 нет конструктора new, вызывается конструктор базового класса Class1. Он создает ссылку на анонимный хеш и связывает его с классом Class2 — ведь именно это имя передано ему в качестве первого параметра. В результате мы получаем неинициализированный объект класса Class2. Затем вызывается метод $self->init, и так как теперь $self — это объект класса Class2, вызывается инициализирующий метод Class2, а не Class1. Конструктор new класса Class1 возвращает объект Class2, инициализированный методом init Class2. Объект $object2 создан.

    Такая схема позволяет сосредоточить команды выделения памяти в одном месте и не дублировать их в каждом из дочерних классов. Поэтому если нам, например, захочется внести изменения в этот процесс, мы можем сделать это в одном месте (а именно, в конструкторе new базового класса Class1), и наши изменения автоматически распространятся на всю цепочку дочерних классов.

    Мы можем еще дальше продолжить процесс «делегирования полномочий» (основной принцип объектно-ориентированного программирования), предоставив инициатору Class1::init заполнять поля класса Class1, а инициатору Class2::init — поля класса Class2, не дублируя работу инициатора Class1::init:

    package Class1;

    sub new

    {

       my $class = shift;

       my $self = {};

       bless $self, $class;

       return $self->init(@_);

    }

    sub init

    {

       my $self = shift;

       $self->{DATA} = shift;

       return $self;

    }

    return 1;

    package Class2;    # конструктор new наследуется

    use Class1;

    @ISA = qw/Class1/;

    sub init

    {

       my $self = shift;

       $self->SUPER::$init(@_);

       $self->{VALUE} = shift;

       return $self;

    }

    return 1;

    use Class1;

    use Class2;

    $object1 = Class1->new(1024);

    $object2 = Class2->new(1024, 3.141592653589);

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

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

    Наследование переменных класса

    Как мы видели в разделе «Создание переменной класса», в Perl можно создавать переменные класса, — то есть, переменные, недоступные вне класса, и содержащие значения, общие для всех объектов класса и доступные им. К сожалению, в Perl нет возможности организовать автоматическое наследование переменных класса, если только вы не организуете доступ к переменным базового класса с помощью наследуемых методов доступа:

    package Class1;

    my $counter = 0;

    sub new {

       my $class = shift;

       my $self = {};

       bless $self, $class;

       return $self;

    }

    sub getcounter{

       return $counter;

    }

    sub setcounter{

       $counter = shift;

       return $counter;

    }

    return 1;

    package Class2;    # конструктор наследуется

    use Class1;

    @ISA = qw/Class1/;

    sub inccounter{

       my $self = shift;

       $my temp;

       $temp = $self->getcounter;

       $temp++;

       $self->setcounter($temp);

       return $temp;

    }

    return 1;

    use Class2;

    $object = Class2->new();

    $object->setcounter(1024);

    print $object->inccounter, "\n";

    print $object->inccounter, "\n";

    print $object->inccounter, "\n";

    1025;

    1026;

    1027;

    При этом возникает трудность: новый класс не просто наследует переменные класса от базового класса, — он использует те же самые переменные, что и базовый класс:

    use Class1;

    use Class2;

    $object1 = Class1->new();

    $object2 = Class2->new();

    print $object1->setcounter(15);

    $object2->inccounter;

    print $object1->getcounter;

    15

    16

    Чтобы новый класс не мешал базовому классу работать с его переменными, придется повторить вручную для нового класса все переменные, которые были определены в базовом классе. Однако и на этом пути вас подстерегает опасность: унаследованные от базового класса методы, напрямую работающие с переменными класса, будут обращаться к переменным для базового, а не дочернего класса:

    package Class2;

    my $counter = 100;

    use Class1;

    @ISA = qw/Class1/;

    sub inccounter{       # косвенный доступ

       my $self = shift;

       $my temp;

       $temp = $self->getcounter;

       $temp++;

       $self->setcounter($temp);

       return $temp;

    }

    sub inccounter2{     # прямой доступ

       my $self = shift;

       $counter++;

       return $counter;

    }

    return 1;

    use Class2;

    $object = Class2->new();

    print $object->inccounter, "\n";

    print $object->inccounter, "\n";

    print $object->inccounter, "\n";

    print $object->inccounter2, "\n";

    print $object->inccounter2, "\n";

    print $object->inccounter2, "\n";

    1

    2

    3

    101

    102

    103

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

    package Class1;

    my $counter = 1;

    sub new {

       my $class = shift;

       my $self = {};

       bless $self, $class;

       return $self;

    }

    sub getcounter{

       return $counter;

    }

    sub setcounter{

       $counter = shift;

       return $counter;

    }

    sub twicecounter{    # косвенный доступ

       my $self = shift;

       $my temp = $self->getcounter;

       $temp = 2*$temp;

       $self->setcounter($temp);

       return $temp;

    }

    return 1;

    package Class2;    # конструктор наследуется

    my $counter = 100;

    use Class1;

    @ISA = qw/Class1/;

    sub getcounter{

       return $counter;

    }

    sub setcounter{

       $counter = shift;

       return $counter;

    }

    sub triplecounter{    # косвенный доступ

       my $self = shift;

       $my temp = $self->getcounter;

       $temp = 3*$temp;

       $self->setcounter($temp);

       return $temp;

    }

    return 1;

    use Class2;

    $object = Class2->new();

    print $object->twicecounter, "\n";

    print $object->triplecounter, "\n";

    200

    300

    Множественное наследование

    В Perl производный класс может наследовать сразу от нескольких классов. Каждый из них должен быть объявлен с помощью команды use и перечислен в массиве @ISA. Например, есть два базовых класса, Class0 и Class1. Класс Class0 содержит метод printhi:

    package Class0;

    sub printhi {print "Hi\n";}

    return 1;

    Класс Class1 содержит метод printhello:

    package Class1;

    sub printhello {print "Hello\n";}

    return 1;

    На базе классов Class0 и Class1 мы создадим класс Class2:

    package Class1;

    use Class0;

    use Class1;

    $ISA = qw/Class0 Class1/;

    sub new

    {

       my $class = shift;

       my $self = {};

       bless $self, $class;

       return $self;

    }

    return 1;

    Если теперь создать объект класса Class2, то можно будет использовать и метод printhi класса Class0, и метод printhello класса Class1:

    use Class2;

    my $object = Class2->new();

    $object->printhi;

    $object->printhello;

    Hi

    Hello

    Обычно после рассказа о синтаксических конструкциях следует описание опасностей и сложностей, возникающих в случае множественного наследования. Данная книга не будет исключением, — в следующем разделе мы узнаем, какие проблемы могут возникнуть в случае неаккуратного применения множественного наследования.

    Множественное наследование: проблемы

    Хотя в большинстве объектно-ориентированных языков программирования разрешено наследование более чем от одного класса, такая возможность порождает больше проблем, чем преимуществ. Например, с точки зрения разработчика компилятора совместить множественное наследование и эффективное использование ресурсов компьютера (память и скорость работы) — нетривиальная задача. К счастью, рассмотрение подобных проблем выходит за рамки данной книги. Отсылаем вдумчивого читателя к специальной литературе, если он/она заинтересовались этим вопросом.

    Однако проблемы имеются и с точки зрения пользователя. Они возникают в случае, когда «по наследству» передаются одноименные поля данных и методы. Возникает ряд вопросов. Какой именно метод и какое именно поле данных (и в какой форме) должны остаться в результирующем объекте? Как в случае существования перекрывающихся полей и методов должны функционировать методы, заимствованные из разных классов? Например, что делать, если поле DATA, в котором наследуемый из класса ClassA метод ожидает найти целое число, совместилось с полем DATA из класса ClassB, который использует его как текстовую строку? Обратная ситуация — что делать, когда и в классе ClassA, и в классе ClassB есть методы getdata, но они выполняют принципиально различные операции? Особым случаем совпадающих имен является вариант, когда оба класса ClassA и ClassB наследуют от общего класса Class0, — должны ли мы сохранить две копии полей данных или достаточно одной? В каком порядке и как следует вызывать конструкторы, чтобы правильно инициализировать поля данных? В каком порядке будут вызываться деструкторы, и как они будут взаимодействовать друг с другом? И подобным проблемам несть числа, стоит только начать разбираться.

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

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

    В случае множественного наследования предполагается, что фундаментальный тип данных, использованный для представления данных объекта, один и тот же для всех родительских классов. Вам не удастся совместить эти классы, если часть из них использует для этой цели массив, а часть — хеш. Даже если все родительские классы используют один и тот же фундаментальный тип данных, все равно наследование полей данных будет зависеть от того, как именно вызываются конструкторы и инициализирующие методы родительских классов (см. далее подраздел «Конструкторы»).

    Если предположить, что в качестве фундаментального типа данных используется ссылка на хеш (наиболее типичный случай), и что при вызове конструктора обеспечивается вызов всех родительских инициализирующих методов (см. далее подраздел «Конструкторы»), то проблем не возникнет, если имена полей данных у родительских классов различны. Их не будет и в том случае, если какой-то из родительских классов участвует в цепочке наследования более одного раза, — его поля данных включаются в дочерний класс однократно, поскольку при всех вызовах инициализирующего метода родительского класса в одни и те же поля заносятся одни и те же значения.

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

    package Class1;

    .....

    sub new

    {

       my $class = shift;

       my $self = {};

       bless $self, $class;

       return $self->init(@_);

    }

    sub init

    {

       my $self = shift;

       $self->{__PACKAGE__ . "Name"} = "Anonymous";

       $self->{__PACKAGE__ . "ID"} = 1024;

       $self->{__PACKAGE__ . "DATA"} = 2.71818182;

       $self->{__PACKAGE__ . "InitList"} = [@_];

       .....

    }

    .....

    return 1;

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

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

    Если родительские классы добавляют непересекающиеся (по именам) методы, то конфликтов не возникает — Perl вполне способен самостоятельно найти нужный метод в иерархической структуре родительских классов (см. раздел «Как реализовать наследование классов»).

    Проблема появляется, если у родительских методов имеются общие имена, но в этом случае всегда можно однозначно сказать, какой из двух или более одноименных методов наследуется дочерним классом. Если Perl не может найти метода или переменной в указанном классе, он проверяет классы, заданные в массиве @ISA в порядке перечисления. Обратите внимание, что если, скажем, требуемый метод не будет найден для первого класса в его списке @ISA, а у этого класса есть свой список @ISA, то Perl поступит следующим образом. Он сначала проверит полное дерево наследования первого класса, а только затем перейдет к поиску недостающего метода или переменной во втором классе, указанном в массиве @ISA исходного класса. Тем самым в Perl реализуется алгоритм поиска «сперва вглубь, потом вширь», решающий проблему множественного наследования.

    Зная порядок, в каком происходит поиск метода, легко установить, какой именно метод наследуется классом. Что делать, если класс должен наследовать другой родительский метод, и этого нельзя добиться, изменяя порядок следования классов в массиве @ISA? Написать новый метод с этим именем, в котором в явном виде указывается, какой из родительских классов является источником наследуемого метода:

    Класс::метод("Класс", ...)  # вызов метода класса

    Класс::метод($self, ...)    # вызов метода экземпляра

    Однако это уже относится к теме замещения методов, которая будет рассмотрена в следующем разделе.

    Замещение методов

    Если вы определяете в дочернем классе метод, имя которого совпадает с именем метода одного или нескольких родительских классов, происходит замещение родительского метода (см. выше раздел «Наследование методов»). Если при этом новый метод никак не использует возможности прежних, обеспечивая полностью независимый и самостоятельный код, вам не надо предпринимать каких-либо дополнительных действий по согласованию наследуемых методов.

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

    package ClassA;

    sub new

    {

       my $class = shift;

       my $self = {};

       bless $self, $class;

       return $self;

    }

    sub printH {return "Bye...";}

    return 1;

    package ClassB;

    use ClassA;

    @ISA = qw/ClassA/;

    # конструктор наследуется, поэтому метода `new’ нет

    sub printH

    {

       my $self = shift;

       my $temp = $self->SUPER::printH;

       return $temp . " by now!";

    }

    return 1;

    use ClassB;

    $object1 = Class2->new();

    print $object1->printH, "\n";

    Bye... by now!

    Тот же подход будет работать и в случае множественного наследования, если у родительских классов нет перекрывающихся методов. Однако при наличии нескольких родительских методов с одинаковыми именами, будет вызван тот метод, который Perl найдет в первую очередь, исходя из принципа «сперва вглубь, потом вширь» (см. предыдущий подраздел). Остальные родительские методы с тем же именем будут игнорироваться. Поэтому если вы хотите, например, последовательно вызвать все родительские методы с данным именем, префикс SUPER не поможет вам.

    Чтобы иметь полный контроль над вызовом родительских методов, их приходится вызывать не как методы, а как подпрограммы с указанием явного имени класса в виде префикса, передавая в качестве первого параметра имя класса или объект (в зависимости от того, является ли метод методом класса или методом экземпляра класса). Пример:

    package Class0;    # базовый класс

    sub new

    {

       my $class = shift;

       my $self = {};

       bless $self, $class;

       return $self;

    }

    return 1;

    package Class1;

    use Class0;

    @ISA = qw/Class0/;

    # конструктор наследуется, поэтому метода `new’ нет

    sub printB {return "Bye";}

    return 1;

    package Class2;

    use Class0;

    @ISA = qw/Class0/;

    # конструктор наследуется, поэтому метода `new’ нет

    sub printB {return "Hello";}

    return 1;

    package Class3;

    use Class1;

    use Class2;

    @ISA = qw/Class1 Class2/;

    sub printB {

       my $self = shift;

       (my $temp, my $name) = @_;

       if ($temp) {

          $temp = Class2::printB($self);

       } else {

          $temp = Class1::printB($self);

       }

       return $temp . ", " . $name;

    }

    return 1;

    use Class3;

    $object = Class3->new();

    print $object->printB(0, "Alex"), "!\n";

    print $object->printB(1, "Sony"), "!\n";

    Bye, Alex!

    Hello, Sony!

    Переменные класса

    Как уже говорилось в разделе «Наследование переменных класса», Perl не содержит встроенных механизмов, позволяющих автоматически наследовать переменные класса. Самое лучшее, что вы можете сделать — это обеспечить доступ к переменным класса только через специальные методы, дублируя для каждого дочернего класса, переменные класса, и методы доступа к ним. (Механизм виртуальных методов Perl будет гарантировать, что будут использоваться нужные переменные класса, даже если вызывается наследуемый от родительского класса метод.)

    То же самое должно быть сделано и в случае множественного наследования, — а именно, надо продублировать все переменные класса всех родительских классов вместе с методами доступа к ним. Поскольку в этом случае происходит полное замещение прежних методов доступа новыми (независимо от того, как именно наследуются методы доступа родительских классов), проблем множественного наследования не возникает.

    Подсказка: Одноименные переменные класса (то есть, одноименные методы доступа к переменным класса), используемые различными родительскими методами, должны соответствовать однородным данным. Иначе могут возникнуть проблемы при вызове методов, унаследованных от разных родительских классов. Если это условие не выполнено, то, чтобы избежать перекрывания, вам придется в родительских классах переименовать переменные класса и/или методы доступа к переменным класса.

    Конструкторы

    Конструктор — частный, но специфический случай наличия среди родительских классов имеется нескольких методов с одним и тем же именем (в данном случае, с именем new). Если предоставить инициативу Perl, то он вызовет только один из родительских конструкторов, — а именно, тот, который будет найден в первую очередь, — и проигнорирует остальные. Как правило, в случае множественного наследования требуется немного другое. Мы хотим вызвать все конструкторы, чтобы инициализировать все унаследованные поля данных.

    Поэтому надо заместить родительские конструкторы новым. Он будет последовательно вызывать конструкторы родительских классов (как это сделать, рассказано в предыдущем подразделе). Однако конструктор — специфический метод, выделяющий память под новый объект, а потому последовательный вызов нескольких конструкторов не приведет к успеху. То есть, если у нас есть класс ClassC, наследующий от классов ClassA и ClassB, то вызов в конструкторе класса ClassC друг за другом конструкторов классов ClassA и ClassB приведет к созданию объекта класса ClassB, а объект, созданный конструктором ClassA, будет просто потерян.

    Чтобы избежать этого нежелательного эффекта, необходимо разбить конструктор на собственно конструктор, выделяющий память под объект, и инициализатор, присваивающий начальные значения полям объекта (как это сделать, рассказывается выше в разделе «Наследование методов: методы инициализации»). Тогда мы можем спокойно наследовать «пустой» конструктор от самого нижнего базового класса иерархии наследования, но должны создать свой собственный инициализирующий метод, последовательно вызывающий инициализирующие методы родительских классов (как это сделать, рассказывается в предыдущем разделе).

    Отметим, что порядок вызова инициализаторов будет определять порядок добавления новых данных в объект. Поэтому, например, если у родительских классов имеются перекрывающиеся поля данных, то инициализатор, вызываемый в последнюю очередь, будет иметь определяющее значение. Это не соответствует порядку, в котором происходит поиск методов при множественном наследовании: класс, идущий первым в списке @ISA, обладает приоритетом по сравнению с остальными. Поэтому в случае инициализаторов предпочтительнее установить порядок вызова, обратный по отношению к порядку следования классов в массиве @ISA:

    package Class0;    # базовый класс

    sub new

    {

       my $class = shift;

       my $self = {};

       bless $self, $class;

       $self->init(@_);

       return $self;

    }

    sub init {

       my $self = shift;

       $self->{ClassName} = "Unknown";

       return $self;

    }

    return 1;

    package Class1;

    use Class0;

    @ISA = qw/Class0/;

    # конструктор наследуется, поэтому метода `new’ нет

    sub init {

       my $self = shift;

       $self->SUPER::init(@_);

       $self->{Name} = "Anonymous";

       $self->{ID} = -1;

       return $self;

    }

    return 1;

    package Class2;

    use Class0;

    @ISA = qw/Class0/;

    # конструктор наследуется, поэтому метода `new’ нет

    sub init{

       my $self = shift;

       $self->SUPER::init(@_);

       $self->{Title} = "IBM PX/XT";

       $self->{ID} = 8088;

       return $self;

    }

    return 1;

    package Class3;

    use Class1;

    use Class2;

    @ISA = qw/Class1 Class2/;

    sub init{

       my $self = shift;

       Class2::init($self, @_);

       Class1::init($self, @_);

       $self->{ClassName} = "CardBox";

       return $self;

    }

    return 1;

    Деструкторы

    Если объект удаляется из памяти системой автоматической сборки мусора, вызывается деструктор объекта (если он существует). В качестве параметра Perl передает деструктору уничтожаемый объект, и этот параметр не должен меняться в результате его работы. Иными словами, нельзя изменять значение первого элемента массива @_ (то есть, значение $_[0]) внутри деструктора, однако сам объект, на который ссылается значение $_[0], менять можно. Например, если вы повторно свяжете объект и класс с помощью функции bless, то вместо уничтожения объекта система автоматической сборки мусора вызовет деструктор повторно (если, конечно, объект по-прежнему подлежит уничтожению — вы можете спасти его, присвоив объект-ссылку глобальной переменной):

    package Class0;

    my $counter = 0;

    sub new

    {

       my $self = {};

       bless $self;

       return $self;

    }

    sub DESTROY

    {

       my self = $_[0];

       if ($counter++ < 4) {

          bless $self;

          print "I am alive ($counter)...\n";

       } else {

          print "That's it!\n";

       }

    }

    return 1;

    use Class0;

    $object = Class0->new();

    exit;    # все объекты будут уничтожены

    I am alive (1)...

    I am alive (2)...

    I am alive (3)...

    That's it!

    Деструкторы Perl не обеспечивают автоматического прохода по дереву наследования, — а именно, даже если у родительских классов вашего объекта существуют деструкторы, они не будут вызываться. Если вы хотите, чтобы при вызове деструктора происходил вызов всех деструкторов родительских классов, вы должны сделать это сами.

    В отличие от деструкторов пакета END, деструкторы объектов DESTROY могут вызываться пользователем в явном виде. Порядок вызовов деструкторов задается самим пользователем и не обязан следовать порядку, в котором инициализируются поля объекта. Пример:

    package Class0;    # базовый класс

    sub new

    {

       my $self = {};

       bless $self;

       return $self;

    }

    sub DESTROY {

       my $self = $_[0];

       print "Class0\n";

    }

    return 1;

    package Class1;

    use Class0;

    @ISA = qw/Class0/;

    sub DESTROY {

       my $self = $_[0];

       print "Class1\n";

       $self->SUPER::DESTROY();

    }

    return 1;

    package Class2;

    use Class0;

    @ISA = qw/Class0/;

    sub DESTROY {

       my $self = $_[0];

       print "Class2\n";

       $self->SUPER::DESTROY();

    }

    return 1;

    package Class3;

    use Class1;

    use Class2;

    @ISA = qw/Class1 Class2/;

    sub DESTROY {

       my $self = $_[0];

       print "Class3\n";

       Class2::DESTROY($self);

       Class1::DESTROY($self);

    }

    return 1;

    use Class3;

    $object = Class3->new();

    $object = 0;    # ссылка на объект потеряна,

                         # объект будет уничтожен

    print "That's all!\n";

    Class3

    Class2

    Class0

    Class1

    Class0

    That's all!

    Инкапсуляция вместо наследования

    Если вы используете множественное наследование, не исключено, что вы можете заменить его множественной инкапсуляцией. А именно, вместо того, чтобы наследовать от родительских классов, вы размещаете объекты родительских классов в качестве полей данных вашего объекта. В таком случае вы сохраняете полный контроль над всеми полями данных и всеми родительскими методами, независимо от того, перекрываются их имена или нет, и в каком порядке родительские классы перечисляются в списке наследования. В качестве платы, однако, вы теряете возможность наследования от родительских классов. Например, если вы хотите вызвать какой-либо метод родительского класса, то должны написать отдельную подпрограмму (метод), в которой вызываете нужный вам метод для объекта, являющегося одним из полей данных инкапсулирующего объекта.

    Связывание переменных

    Perl позволяет привязывать переменные к классам, в результате чего изменение содержимого переменной сопровождается вызовом методов класса. Эта возможность впервые появилась в Perl версии 5 и отсутствовала в ранних версиях (равно как и сами классы вместе с объектно-ориентированным подходом).

    Чтобы связать переменную и класс, используется функция tie:

    tie переменная, имя-класса, список

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

    После того, как переменная и класс связаны (для этого класс должен соответствовать определенным требованиям — см. следующие разделы), в дело вступает магия: при попытке прочитать значение переменной или присвоить ей новое значение, скрытым от пользователя способом вызываются соответствующие методы класса. Они вызываются неявно, подобно конструкторам и деструкторам пакетов BEGIN и END. Соответственно, методы имеют фиксированные имена, составленные из заглавных букв, и не должны использоваться напрямую.

    Функция tie возвращает в качестве значения объект, — то есть ссылку, помеченную (с помощью функции bless), как объект соответствующего класса. Для создания такого объекта используется один из специальных методов класса — TIESCALAR, TIEARRAY или TIEHASH (в зависимости от типа переменной). Работа этих методов рассматривается ниже в соответствующих разделах.

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

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

    $объект = tied переменная

    Если аргумент функции tied не является связанной переменной, возвращается неопределенное значение, — то есть, с точки зрения логики Perl, значение ложь.

    Наконец, вы можете разрушить связь переменной и класса, вызвав функцию untie:

    untie переменная

    Будет ли в результате работы функции untie разрушен объект, связанный с переменной, зависит от того, сохранилась ли где-либо на него ссылка. Если нет, вступает в действие система автоматического сбора мусора. Если в какой-то переменной осталось значение объекта-ссылки, он продолжит свое существование, но связь между ним и переменной будет разрушена — то есть, при чтении или записи значений переменной методы объекта больше не будут вызываться.

    Подсказка: Подмодули модуля Tie (а именно, пакеты Tie::Scalar, Tie::StdScalar, Tie::Array, Tie::StdArray, Tie::Hash, Tie::StdHash, Tie::RefHash, Tie::SubstrHash и Tie::Handle) можно использовать как базовые классы при создании классов, связываемых с переменными того или иного типа.

    Связывание скалярных переменных

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

    Чтобы связать скалярную переменную и класс, последний должен содержать специальные методы с фиксированными именами и аргументами:

    TIESCALAR класс, список-значений

    FETCH ссылка

    STORE ссылка, значение

    DESTROY ссылка

    Метод TIESCALAR связывает класс и скалярную переменную. Первым параметром выступает имя класса, следом идет список дополнительных значений (возможно, отсутствующий), который пользователь хочет передать процедуре связывания. Он вызывается функцией tie опосредовано, и поэтому в число его параметров не входит переменная, которую связывают с классом. Метод должен возвращать объект класса, — а именно, ссылку, связанную с классом с помощью функции bless. Этот объект будет использоваться при всех операциях со связанной переменной. (Как следует из приводимого ниже примера, объект является независимым от переменной и может отличаться от ссылки на связанную переменную.)

    Метод FETCH вызывается при попытке прочитать значение переменной. В качестве параметра ему передается объект, связанный с переменной. В качестве значения должен возвращаться результат чтения переменной (возможно, откорректированный).

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

    Метод DESTROY вызывается в случаях, когда скалярная переменная, связанная с классом, уничтожается процедурой автоматической сборки мусора (например, в результате выхода за пределы области видимости переменной). В качестве параметра передается объект, связанный с переменной. Возвращаемая величина значения не имеет. Подобно деструкторам пакетов и объектов, данный метод по большей части ничего не выполняет — ведь в Perl выделение и освобождение выделенной памяти происходит автоматически.

    Подсказка: Пакеты Tie::Scalar и Tie::StdScalar можно использовать как базовые, при создании классов, связываемых со скалярами.

    После полученных разъяснений можно посмотреть на реализацию класса Double:

    package Double;

    $data = 0;      # глобальная переменная пакета

    sub TIESCALAR

    {

       my $class = shift;

       $data = shift;

       return bless \$data, $class;

    }

    sub FETCH

    {

       my $self = shift;

       return 2*$data;

    }

    sub STORE

    {

       my $self = shift;

       $data = shift;

       if ($data < 0) {$data = 0;}

       return 2*$data;

    }

    sub DESTROY { }

    return 1;

    Теперь мы должны связать скаляр и класс Doubler, — для этого используем функцию tie:

    use Doubler;

    tie $mydata, 'Doubler';

    $mydata = 5;

    print "\$data evalueates to $mydata";

    \$data evalueates to 10

    Как и следовало ожидать, отправив в переменную число 5, на выходе мы получили число 10.

    Отметим, что приведенный выше код, далек от оптимального, — в каждый момент мы можем связать с классом только одну скалярную переменную, поскольку в качестве хранилища поля данных объекта выступает статическая глобальная переменная. Используя динамически размещаемые анонимные скаляры, (обратите внимание, как мы используем технологию «автооживления ссылок»), можно избавиться от указанного недостатка:

    package Double;

    sub TIESCALAR

    {

       my $class = shift;

       my $self;

       ${$self} = shift;

       return bless $self, $class;

    }

    sub FETCH

    {

       my $self = shift;

       return 2*${$self};

    }

    sub STORE

    {

       my $self = shift;

       my $temp = shift; if ($temp < 0) {$temp = 0;}

       ${$self} = $temp;

       return 2*$temp;

    }

    sub DESTROY { }

    return 1;

    Связывание массивов

    Вы можете связывать с классом не только скаляры, но и массивы. Для этого класс должен содержать следующие методы:

    TIEARRAY класс, список-значений

    FETCH ссылка, индекс

    STORE ссылка, индекс, значение

    DESTROY ссылка

    FETCHSIZE ссылка

    STORESIZE ссылка, число

    Метод TIEARRAY связывает класс и переменную-массив. Первым параметром выступает имя класса, следом идет список дополнительных значений (возможно, отсутствующий), который пользователь хочет передать процедуре связывания. Если пользователь обратился к функции tie, этот метод вызывается Perl неявно, (поэтому в число его параметров и не входит массив, который связывается с классом). Метод должен возвращать объект класса, — а именно, ссылку, связанную с классом посредством функции bless. Этот объект будет использоваться при всех операциях со связанной переменной. Как и в случае связывания скаляров, объект является независимым от переменной и может отличаться от ссылки на связанную переменную.

    Метод FETCH вызывается при попытке прочитать значение элемента массива. В качестве первого параметра ему передается объект, связанный с массивом. В качестве второго — индекс массива, указанный при операции чтения. В качестве значения должен возвращаться результат чтения элемента массива (возможно, откорректированный).

    Метод STORE вызывается при попытке присвоить элементу массива новое значение. В качестве первого параметра ему передается объект, связанный с массивом. В качестве второго — индекс массива, указанный при операции чтения. Третьим параметром является значение, которое пользователь хочет присвоить элементу массива. Полезно, если в качестве результата метод возвращает значение, которое (возможно) было бы получено при последующем чтении содержимого этого элемента массива.

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

    Метод FETCHSIZE возвращает текущую длину массива. Он вызывается, когда пользователь использует конструкцию scalar(@имя-массива) или $#имя-массива. В качестве параметра передается объект, связанный с массивом. В качестве значения метод должен вернуть число, — текущее значение длины массива.

    Метод STORESIZE устанавливает новую длину массива. Например, он вызывается, когда пользователь использует конструкцию вида $имя-массива[индекс] со значением индекса, выходящим за пределы текущей длины массива. Однако он может вызываться и независимо. Первым параметром является объект, связанный с массивом, вторым — длина массива, которую надо установить. Если новая длина больше текущей, появляющиеся элементы должны получить значение undef. Если — меньше, элементы за пределами верхней границы должны быть удалены.

    Кроме перечисленных методов, класс может содержать необязательные методы с именами EXTEND, SHIFT, UNSHIFT, POP, PUSH, SPLICE, CLEAR. Они вызываются в случае применения к связанной переменной соответствующих функций работы с массивами. Описание формата и действий этих функций можно найти в документации, сопровождающей Perl.

    Подсказка: Пакеты Tie::Array и Tie::StdArray можно использовать как базовые при создании классов, связываемых с массивами. В частности, оттуда можно наследовать методы EXTEND, SHIFT, UNSHIFT, POP, PUSH, SPLICE, CLEAR, выполняющие действия, которые имитируют работу с настоящими массивами, а также найти документацию по формату этих методов.

    После полученных разъяснений рассмотрим в качестве примера класс Darray, который удваивает значения элементов массива при их чтении, хотя запоминает их без искажений:

    package Darray;

    sub TIEARRAY

    {

       my $class = shift;

       my $self = [];

       @{$self} = @_;

       bless $self, $class;

       return $self;

    }

    sub FETCH

    {

       my $self = shift;

       my $index = shift;

       return 2* ${$self}[$index];

    }

    sub STORE

    {

       my $self = shift;

       my $index = shift;

       ${$self}[$index] = shift;

       return 2* ${$self}[$index];

    }

    sub FETCHSIZE 

    {

       my $self = shift;

       return ($#{$self}+1);

    }

    sub STORESIZE 

    {

       my $self = shift;

       $#{$self} = shift (@_)-1;

    }

    sub DESTROY { }

    return 1;

    Вот результат работы нашего примера, — извлеченные из массива значения удваиваются:

    use Darray;

    tie @array, "Darray", (1, 2, 3);

    print join (", ", @array);

    2, 4, 6

    Связывание хешей

    Чтобы связывать классы и хеши, класс должен содержать следующие методы:

    TIEHASH класс, список-значений

    FETCH ссылка, ключ

    STORE ссылка, ключ, значение

    DESTROY ссылка

    DELETE ссылка, ключ

    CLEAR ссылка

    EXISTS ссылка, ключ

    FIRSTKEY ссылка

    NEXTKEY ссылка, последний-ключ

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

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

    Подсказка 2: Кроме пакетов Tie::Hash и Tie::StdHash, в состав Perl входят пакеты Tie::RefHash (использование ссылок в качестве ключей хеш-таблицы), Tie::SubstrHash (связывание классов и хеш-таблиц фиксированного размера и с фиксированной длиной ключа), Tie::Handle (связывание дескрипторов файлов и классов). Если вы хотите расширить свой инструментарий, используемый при программировании на Perl, настоятельно советую с ними ознакомиться.

    Использование класса Perl UNIVERSAL

    Все классы Perl неявно наследуют от общего базового класса UNIVERSAL. (Имя этого класса подразумевается Perl в конце массива @ISA.) Начиная с версии Perl 5.004, класс UNIVERSAL содержит встроенные методы: isa, can, VERSION.

    Метод isa проверяет, является ли объект объектом класса, выводимым (через цепочку наследования) из другого класса. С этой целью он использует цепочку имен, начинающуюся от массива @ISA класса, к которому относится текущий объект. Например, вот так мы можем проверить, что прародитель объекта класса Math::Complex — класс UNIVERSAL:

    use Math::Complex;

    $complesnumber = Math::Complex->new(1, 2);

    if ($complexnumber->isa("UNIVERSAL")) {

       print "OK";

    } else {

       print "Not OK";

    }

    OK

    Точно также можно проверить, что Math::Complex наследует от класса Exporter (поскольку Exporter перечислен в массиве @ISA класса Math::Complex):

    if ($complexnumber->isa("Exporter")) {

       print "OK";

    } else {

       print "Not OK";

    }

    OK

    С другой стороны, для класса Math::BigFloat мы получим прямо противоположный результат:

    if ($complexnumber->isa("Math::BigFloat")) {

       print "OK";

    } else {

       print "Not OK";

    }

    Not OK

    Обратите внимание, что хотя метод isa и не является встроенным в Perl, мы использовали его без импортирования и указания имени пакета UNIVERSAL в качестве префикса (поскольку метод isa расположен именно в этом пакете) — просто потому, что унаследовали его от класса UNIVERSAL через класс Math::Complex.

    Метод isa может использоваться также и для того, чтобы проверять, на какой тип данных ссылается ссылка. Он работает в том числе и тогда, когда тип данных, который мы проверяем, не является классом:

    use UNIVERSAL "isa";

    $ref = [1, 2, 3];

    if (isa($ref, 'ARRAY') {

       print "OK";

    } else {

       print "Not OK";

    }

    OK

    Обратите внимание, что в этом случае нам пришлось импортировать функцию isa в явном виде.

    Подсказка: Встроенная функция Perl ref тоже может использоваться для определения типа данных, на который указывает ссылка. Так, в предыдущем примере ref($ref) возвращает строку 'ARRAY', показывающую, что ссылка указывает на массив. Элементарными типами данных являются 'REF', 'SCALAR', 'ARRAY', 'HASH', 'CODE', 'GLOB'. Если аргумент функции — не ссылка, возвращается значение ложь. Существенно, что для ссылок, сцепленных с классом с помощью функции bless, в качестве результата возвращается имя класса, а не тип, использованный для хранения данных.

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

    $datacall = $object->can('getdata');

    if ($datacall) ...

    Метод VERSION класса UNIVERSAL позволяет выяснить, определена ли в теле класса глобальная переменная $VERSION (Perl использует значение этой переменной для проверки версий пакетов). Вот как мы задаем версию пакета:

    package Class1;

    $VERSION = 1.01;

    sub new

    {

       my $self = { };

       bless $self;

       return $self;

    }

    return 1;

    Теперь с помощью метода VERSION можно проверить версию пакета Class1:

    use Class1;

    $object = Class1->new();

    print $object->VERSION;

    1.01

    Метод может вызываться (и, как правило, вызывается), как метод класса:

    use Class1;

    print Class1->VERSION;

    1.01

    В приведенных выше примерах мы использовали метод VERSION без параметров. У метода можно задать необязательный параметр, заставляющий его проверять, не меньше ли текущая версия пакета заданной (если версия пакета устарела, выполняется команда die):

    use Class1;

    print Class1->VERSION(1.02);

    Class1 version 1.02 required—this is only version 1.0