Константы _CLASS_ и _METHOD_

Мы уже рассматривали предопределенные константы _FILE_ и _LINE_, которые заменяются РНР именем файла и номером текущей строки. Существуют еще две константы, которые "работают" аналогичным образом и могут быть целях.

__CLASS__

Заменяется РНР именем текущего класса.

__МЕТНОD__

Заменяется интерпретатором на имя текущего метода (или имя функции, если определяется функция, а не метод).

Позднее статическое связывание

У конструкции self и константы _ CIASS _ имеется ограничение: они не позволяют переопределить статический метод в производных классах. В листинге 7 в производном классе Child осуществляется попытка переопределить статический метод ti tle(), ранее определенный в базовом классе Base.

Листинг 7 self не позволяет переопределить метод. Файл inherit_static.php

Как видно из примера, при попытке воспользоваться методом test() в классе­наследнике, self, вместо того чтобы вернуть метод из класса Child, вызвал метод из базового класса Ваsе. Для решения этой проблемы РНР предоставляет специальное ключевое слово static, которое можно задействовать вместо self (листинг 8).

Листинг 8 static позволяет переопределить метод. Файл static.php

Анонимные классы

Начиная с РНР 7, доступны анонимные классы. Для их демонстрации создадим класс Dumper, который имеет единственный статический метод, выводящий дамп своего аргумента (листинг 9).

Листинг 9 Использование анонимных классов. Файл anonym.php

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

При определении анонимного класса внутри другого класса, для получения доступа к защищенным членам допускается наследование анонимных классов (листинг 10).

 

Обратите внимание, что в примере анонимный класс возвращается оператором return, в конце которого обязательно требуется точка с запятой. Анонимный класс, будучи унаследованным от класса container, получает доступ к защищенному члену $id. в то время как закрытый член $title мы вынуждены были передать через его конструктор. Результатом выполнения скрипта будет следующая строка:

Класс Container (1)

Полиморфизм

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

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

Абстрагирование

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

· статические страницы (Главная страница, Контакты, О компании, Вакансии);

· новости;

· каталог;

· личный кабинет пользователя (Вход, Регистрация, И1менение настроек).

Введем для этих типов страниц различные классы, которые будут хранить содержимое страниц. Так для статических страниц можно ввести класс StaticPage, для новостей - News, для каталога - catalog, для обслуживания пользователей - user.

Несмотря на то, что страницы выполняют разные функции, у них имеются общие черты. Так каждая страница имеет название ($title), содержимое ($content), часть страниц, например новости и каталог, могут содержать изображение ($image). Все классы должны обеспечивать вывод страницы, т. е. иметь метод генерации, например, render().

Разумеется, классы страниц могут и отличаться. Так для работы с пользователем могут потребоваться его имя (Snickname), пароль ($password), электронный адрес (Semail). Статические страницы могут кэшироваться, новости, каталог могут кэшироваться для того, чтобы снизить нагрузку на базу данных, а личный кабинет пользователя кэшировать не представляется возможным, иначе из кэша могут выдаваться чужие страницы.

ПРИМЕЧАННЕ

Диаграммы классов и объектов принято выражать в специальном графическом языке UML, который имеет много тонкостей и позволяет выразить все отношения, встречающиеся в современных объектно-ориентированных системах.

Попробуем отразить описанные выше требования в графической диаграмме (рис. 1).

Все классы имеют общий базовый класс Page, который содержит общие для всех классов члены ($title, $content) и методы (render()).Классы, которые должны кэшировать страницы, так же наследуются от общего класса cached. Обратите внимание, что cached сам является производным классом от Page, поэтому содержит все переменные и методы, которые определены в нем. Страницы пользователей (класс User) мы договорились не кэшировать, поэтому они наследуются от Page напрямую.

Рис. 1. Диаграмма классов

Таким образом, каждый из классов будет обладать методом render(), даже если он полностью переопределяет его поведение. Если нам потребуется RSS-страница, которая не должна содержать шаблон сайта и выдавать данные в ХМL-формате, класс страницы все равно будет содержать метод render (), хотя он будет сильно отличаться по реализации от аналогичных методов других классов. Так единообразие интерфейса, заданное в базовом классе, и называется полиморфизмом.

Обратите внимание, что в момент формулировки требований мы не уточняем, какой именно странице (новости, о нас, RSS-канал и т. д.) соответствуют объекты Page и cached. Нам это неизвестно, да и не нужно знать. Мы в будущем хотим свободно добавлять новые типы страниц, ведущие себя похожим образом и удовлетворяющие всем тем же требованиям.

ПРИМЕЧАНИЕ

Такой способ описания класса Page, коrда до конца неизвестно, что именно он будет делать в программе, называют абстрагированием, а сам класс - абстрактным.

В листинге 11 показано определение базового класс Page, удовлетворяющее описанным выше условиям. Мы помещаем в него только код, являющийся общим для всех типов страниц. Иными словами, члены и методы, которые должны присутствовать в каждом классе, мы собираем в одном месте, дабы не размножать их.

Листинг 11 Базовый класс страницы. Файл pages/Page.php

Каждая страница содержит название $title и содержимое $content, которые устанавливаются в конструкторе при создании класса. Обратите внимание на то, что мы стараемся не обращаться к этим переменным напрямую, вместо этого используем методы ti tle() и content(), что позволит нам в производных классах перегрузить эти методы, например, с целью извлечения их быстрого кэша, расположенного в оперативной памяти. Метод render() будет использовать методы ti tle() и content() текущего класса независимо от того, будет он сам перегружаться или нет. Давайте попробуем определить класс cached, который будет реализовывать логику хранения поступающих страниц в каком-нибудь NоSQL-хранилище, например, memcached.

В листинге 12 представлена одна из возможных реализаций класса Cached. Так как в нет возможности детально рассмотреть memcached, всю информацию помещения и извлечения данных в кэш мы оставим закомментированной.

Листинг 12 Базовый класс для кешируемых страниц. Файл pages/Cached.php

Как видно из листинга 12, в конструкторе класса Cached мы вызываем конструктор базового класса при помощи ключевого слова parent. Обратите внимание, что для конструктора, в отличие от других методов, разрешается изменять состав аргументов. Для того чтобы оперировать кэшем, нам потребуются минимум три метода:

· isCached() - проверка, помещено ли значение в кэш;

· set() - размещение значения в кэше;

· get() - извлечение значения из кэша.

Кроме этого дополнительно мы вводим метод id(), который возвращает уникальный ключ, для хранилища.

Кроме того, мы изменяем логику поведения методов title() и content () таким образом, чтобы они извлекали данные из memcached.

Виртуальные методы

Давайте взглянем на листинг 12 (конструктор класса) и посмотрим, как реализовано кэширование, что называется, "на низком уровне".

При поступлении значений $title и $content в конструктор осуществляется попытка сохранить их в кэш при помощи метода set() Для этого при помощи метода id() формируется уникальный ключ для каждого из значений. Внутри метода set() проверяется, нет ли записей с таким ключом, если нет - вызывается метод set() memcache, если есть - никакие действия не предпринимаются.

Загвоздка заключается в методе id(). У нас будет множество типов страниц: статические страницы, новости, каталоги, страница регистрации. Причем каталоги и новости будут содержать как индексную страницу со списком новостей и товарных позиций, так и детальные страницы с подробным описанием каждой позиции. Ключи в NоSQL­хранилищах, как правило, хранятся в одном большом списке и должны быть уникальны. Поэтому ответственность за генерацию уникального значения будет возложена на производные классы, которые должны будут перегружать метод id (). Вот тут в силу и вступают так называемые виртуальные методы.

Виртуальным называют метод, который может переопределяться в производном классе.

У нас есть два виртуальных метода - title() и content(). Метод render() базового класса перегрузке не подвергается, тем не менее, использовать он теперь будет новые перегруженные методы.

Еще один виртуальный метод id() мы даже не знаем, как должен быть "устроен", потому что у нас еще нет информации о типе страницы. Таким образом, вызывать виртуальный метод id() пока бессмысленно, что подчеркивается запуском встроенной функции die() в нем.

ПРИМЕЧАНИЕ

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

Раз в базовом классе Cached виртуальный метод id() "вырожден" и является абстрактным, нам обязательно нужно переопределить его в производном классе (листинг 13).

В листинге приводятся закомментированные строки для работы с СУБД через расширение PDO.

 

Листинг 13 Статические страницы. Файлы pages/StaticPage.php

Конструктор класса статических страниц принимает единственный параметр - идентификатор записи в базе данных $id. Он используется для формирования уникального ключа в методе id(). Идентификаторы, как правило, уникальны в пределах таблицы, т. е. в нашем случае в пределах статических страниц. Однако в новостях может встретиться запись с таким же идентификатором, чтобы новости не затирали ключи статических страниц, и наоборот, в методе id() ключ формируется из префикса "static_page_" и идентификатора.

BHHMAHHE!

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

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

Не составит труда теперь создать класс для отображения детальной страницы новости (листинг 14). Для краткости мы опустим индексную страницу со списком новостей.

Рис. 2. Кэширование данных из базы данных а memcache

Листинг 14 Новости. Файлы pages/News.php

Как видно из листинга 14, поменялись лишь префикс news_ в методе id() и название таблицы в SQL-зaпpoce, поэтому класс category не приводим, он полностью аналогичен.

Разумеется, в нашем игрушечном примере многого не хватает, начиная с подсистемы маршрутизации и заканчивая сбросом кэша. Тем не менее, он демонстрирует принцип полиморфизма и перегрузку методов.

ПРИМЕЧАНИЕ

Как показывает пример выше, виртуальный метод не обязательно должен быть абстрактным. Довольно распространена ситуация, когда он сам выполняет некоторые действия и даже вызывается из переопределенного метода.

Давайте рассмотрим, как использовать один из наших новых классов staticPage (листинг 15).

Листинr 15 Проверка виртуальных методов. Файлы pages/test.php

Результатом работы этого скрипта будут строки:

Для закрепления материала снова взгляните на определение класса Page из листинга 11. Итак, хотя метод Page:: render() использует $this->title () и $this->content (), вызываются не функции Page::title() и Page::content (), а функции title() и content() из класса StaticPage! Методы staticPage:: title() и StaticPage::content() просто переопределили функции Page:: title() и Page::content().

ПРИМЕЧАННЕ

Напоминаем еще раз, что функция, переопределяемая в производном классе, называется виртуальной.

Расширение иерархии

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


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: