WDH PERL - Класи та об’єкти

Після короткого знайомства з модулями ми можемо перейти до детального опису їх застосування для створення об'єктно-орієнтованих програм. До об'єктного програмування ставляться по-різному: одні як до єдиного правильного способу програмування, інші як до модної примхи. Автор вважає, що об'єктний підхід дозволяє швидко та чітко формалізувати постановку завдання мовами програмування, тому подальший опис створення модулів та роботи з ними побудовано саме на фундаменті цього підходу.

Як відомо, об'єктно-орієнтоване програмування ґрунтується на двох базових поняттях: класи та об'єкти. Клас - це абстракція, що описує деякі властивості (дані класу та методи роботи з ними). Об'єкт - це конкретний екземпляр класу, що реалізує його властивості.

PERL не містить ні класів, ні об'єктів у традиційному їхньому розумінні. Тим не менш, об'єктне програмування на ньому цілком можливо, якщо керуватися трьома правилами, сформульованими творцями цієї мови:

Давайте розглянемо ці правила докладніше.

6.8.2. Створення класів

У модулі Info::Browser ми не будемо користуватися модулем Explorer, оскільки він не експортує жодних символів. Насамперед, ми повинні визначити структуру даних нашого класу. Найчастіше структура класів реалізується безіменними асоціативними масивами, у яких поля даних індексуються їх назвами. Для модуля Info::Browser ми використовуємо асоціативний масив з трьома полями даних: рядок NAME (назва оглядача), число VERSION (номер версії) та масив OPTIONS з двох логічних значень (перше значення вказує, чи дозволено виклик Java-аплетів, друге дозволено чи куки).

Далі нам потрібен спосіб, який створює об'єкти цього класу. Такі методи називаютьсяконструкторами і можуть мати будь-яке ім'я. Однак, більшість програмістів на PERL називають їх або ім'ям new(), або ім'ям класу. Наш конструктор виглядає так:

Ця функція повідомляє вміст посилання ref, що він є об'єктом класу class. Якщо class опущений, використовується поточний пакет. Функція bless повертає посилання ref, тому операторreturn у нашому конструкторі не є обов'язковим і введений тільки для ясності.

Тепер ми готові написати повний текст модуля Info:: Browser. Крім конструктора, він містить три методи name(), version() і options(), які забезпечують доступ до полів даних наших об'єктів. Ці методи випливають з такої угоди: якщо вони викликані без аргументів, то повертають поточне значення поля, а якщо з аргументом, то заносять його у відповідне поле даних.

Наступний приклад показує, як створити об'єкт класу Info::Browser та ініціалізувати його поля:

Зауважте, що користувачеві нашого модуля не потрібні подробиці його реалізації. Він просто викликає конструктора, а потім звертається до його методів.

Створений нами модуль цілком працездатний, але його конструктор має два недоліки. По-перше, він викликається лише як метод класу, але не як метод об'єкта. Іншими словами, ми не можемо написати

По-друге, функція bless у ньому жорстко прив'язана до класу Info::Browser, оскільки ми викликаємо її без другого аргументу. Якщо ми надалі захочемо створювати класи, які є нащадками Info::Browser, то маємо викликати bless з двома аргументами, пам'ятаючи, що будь-якому методу класу цей клас передається як перший аргумент. З урахуванням цих зауважень наш конструктор набуває вигляду:

Усі наведені вище методи працювали з полями даних об'єкта. Зрозуміло, можливості методів цим необмежені. Ми можемо, наприклад, додати до нашого класу такий метод:

6.8.3. Деструктори об'єктів

Більшість мов програмування, що підтримують створення об'єктів через конструктори, підтримують деструктори об'єктів. Деструктор - це метод, який автоматично викликається середовищем, коли відбувається видалення об'єкта. На відміну від конструктора він повинен мати єдине можливе ім'я, зокрема DESTROY. Причина цього полягає саме в тому, що він найчастіше викликається автоматично системою складання сміття, яка вимагає, щоб усі деструктори називалися однаково.

Хоча деструктори об'єктів у PERL і передбачені, писати їх доводиться вкрай рідко, знову ж таки завдяки наявності системи складання сміття. По суті, єдина ситуація, в якій написання деструктора необхідне, це об'єкти, що містять пряме або опосередковане посилання на себе. Такий об'єкт ніколи не буде автоматично видалений (у всякому разі, поки програма не завершиться), і, якщо ми стурбовані оптимізацією пам'яті, то маємо явно викликати деструктор цього об'єкта.

6.8.4. Дані класу

Досі всі дані нашому класі були даними об'єкта, т. е. ставилися до конкретному екземпляру класу. Проте, у часто бувають потрібні змінні, які стосуються класу загалом. У прикладі такої змінної могла б бути змінна $Count, що містить кількість існуючих екземплярів класу. З її використанням початок модуля виглядає так:

Як бачимо, додалися метод total(), що повертає значення $Count, і деструктор об'єктів, необхідний правильного підрахунку існуючих об'єктів.

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

6.8.5. успадкування

Все об'єктно-Орієнтовані системи програмування включають поняття успадкування. Спадкування означає, що створюваний клас може бути оголошений нащадком вже існуючого класу. Нащадок класу успадковує всі властивості, але може додатково мати власні властивості або змінювати успадковані властивості.

PERL не підтримує спадкування класів. Натомість кожен пакет містить змінну @ISA, що керує успадкуванням методів. Якщо ми викликаємо метод класу або об'єкта, якого немає в пакеті даного класу, PERL переглядає пакети, перелічені в @ISA, у пошуках методу з таким ім'ям. Розглянемо такий пакет:

Цей короткий модуль створює новий клас Info:: System, який успадковує всі методи Info::Browser. Наприклад, ми могли б написати таку програму:

Подивимося, що відбувається під час виклику методу Info::System->new(). Оскільки у пакеті Info::System такого методу немає, PERL шукає його в масиві @ISA і знаходить у пакеті Info::Browser. Потім він викликає цей метод, передаючи йому як перший аргумент ім'я класу Info::System, тобто даний оператор замінюється на Info::Browser::new("Info::System"). Після цього спрацьовує функція bless, другим аргументом якої є назва переданого класу, як описано вище. В результаті ми отримуємо посилання на новостворений об'єкт класу Info::System, що нам потрібно.

Тепер ми можемо додати до класу Info::System його власні методи, наприклад:

Більш того, ми можемо перевизначити в Info::System частину методів класу Info::Browser, наприклад:

Оскільки @ISA є масивом, ніщо не забороняє нам реалізувати на PERL множинне спадкування класів, тобто створити клас, який є нащадком відразу кількох класів. Множинне успадкування - цевелика тема, яка потребує окремого розгляду, тому ми її тут торкатися не будемо.

6.8.6. Клас UNIVERSAL

Справжні поціновувачі PERLа вважають, що вся краса цієї мови в її замовчуваннях, і тому пишуть на ній так, щоб непосвячений нічого не зрозумів. Навіть не поділяючи цього підходу, слід визнати, що знання замовлень PERL часто є корисним. Одним із таких корисних дій за умовчанням є таке: PERL завжди неявно додає до кінця масиву @ISA ім'я модуля UNIVERSAL. Інакше кажучи, всі класи PERLа успадковують методи класу UNIVERSAL. Таких методів три: can, isa та VERSION.

Метод VERSION

Зазвичай цей метод явно не викликається; його викликає функція use() під час перевірки версії. Для того щоб забезпечити перевірку версії свого модуля, потрібно просто додати до нього рядок виду

6.8.7. Безіменні підпрограми як об'єкти

Реалізація об'єктів у вигляді безіменних асоціативних масивів, якою ми досі користувалися, не є, зрозуміло, єдино можливою. Тут наводиться альтернативна реалізація класу Info:: Browser, що базується на безіменних підпрограмах. Вона дещо складніша і повільніша, але має одну істотну перевагу: дані об'єкта стають повністю прихованими від зовнішнього світу. Новий варіант нашого класу має вигляд:

6.8.8. Зв'язування змінних

Наш приклад OutFile приховує за пов'язаною змінною запис у текстовий файл. При зв'язуванні змінної із класом OutFile викликається його конструктор, який створює файл із заданим ім'ям. Якщо видалити зв'язок, цей файл закривається. При кожному присвоюванні пов'язаної змінної нового значення це значення виводиться у файл як новий рядок. Відповідний пакет OutFile.pm матиме вигляд:

Тепер ми можемо написати програму,використовує клас OutFile. Для цього нам знадобляться вбудовані функції PERL, що створюють та видаляють зв'язок змінних з об'єктами. Існують три функції роботи зі зв'язаними змінними: tie(), tied() та untie().

Функція tie встановлює зв'язок змінної із класом. Вона має вигляд

(Якщо функції tied передана незв'язана змінна, вона повертає невизначене значення.) Для розриву зв'язку змінної з об'єктом використовується функція

При розриві зв'язку неявно викликає деструктор об'єкта, що з змінної.

Тепер ми можемо написати приклад використання змінної, пов'язаної з класом OutFile:

Виконавши цю програму, ми знайдемо у поточному каталозі новий файл MYFILE.TXT, що містить такі рядки:

First Second Third

Найчастіше зв'язування змінних використовується встановлення неявного зв'язку асоціативних масивів з базами даних, але це виходить межі нашого довідника.