Лабораторная работа №8. Интерфейсы и трейты.
До сих пор мы подразумевали, что у каждого производного класса может быть только единственный базовый. Наследовать свойства и методы сразу нескольких классов (например, писать class А extends в, с, D) в РНР нельзя.
Большинство современных языков программирования (Java, С# и т. д.) отказались от реализации множественного наследования, потому что она очень усложняет логику программы и добавляет много неоднозначностей в код.
Однако окружающий нас мир не укладывается в рамки иерархических моделей, а если и укладывается, то с большим трудом. Поэтому, несмотря на отказ от множественного наследования, все языки, поддерживающие объектно-ориентированный подход, предоставляют либо саму возможность множественного наследования (С++, Perl), либо какие-то компенсационные механизмы. РНР следует второму пути и предоставляет их целых два: интерфейсы и трейты.
Интерфейсы
Вернемся к схеме страниц, которую мы рассматривали в предыдущей работе.
Допустим, нам необходимо добавить на страницу, помимо заголовка, дополнительную SЕО-информацию: ключевые слова, МЕТА-описание страницы, оg-теrи для социальных сетей. Причем добавлять эту информацию на уровне класса Page может оказаться не очень разумной тактикой, т. к. ряд страниц, например User, точно не будут снабжаться SЕО-информацией. Часть страниц будет снабжаться ею лишь избрано.
|
|
Мы могли бы добавить SЕО-информацию на уровне класса Cached, однако класс несет совсем другую функцию и его нужно переименовывать. Кроме того, у нас получается ограничение, что любая кэшируемая страница снабжается SЕО-информацией, и наоборот, если мы захотим кэшировать страницу, то в нагрузку мы получим SЕО-блок.
Для решения данной проблемы предназначены интерфейсы. Интерфейс (interface) представляет собой обычный абстрактный класс, но только в нем не может быть свойств, и, конечно, не определены тела методов. Фактически некоторый интерфейс указывает лишь список методов, их аргументы и модификаторы доступа (обычно только protected и puЬlic). Допускается также описание констант внутри интерфейса (ключевое слово const).
Класс, реализующий некоторый интерфейс, обязан содержать в себе определения всех методов, заявленных в интерфейсе. Это объясняет, почему в мире ООП интерфейс часто называют контрактом: любой класс, "подписавшись в контракте", обязуется "выполнять" его "условия". Если хотя бы один из методов не будет реализован, вы не сможете создать объект класса: возникнет ошибка.
Главное достоинство интерфейсов заключается в том, что класс может реализовывать интерфейсы независимо от основной иерархии наследования (рис. 1).
|
|
Рис. 1. Интерфейс seo реализуют лишь, те классы, где он требуется
Более того, класс может реализовывать сразу несколько интерфейсов. Например, диаграмму на рис. 1 можно расширить интерфейсом тегов (Tаg): страницы, снабженные тегами, выводят в конце их список. Теги представляют собой гиперссылки, которые ведут к списку материалов, снабженных такими же тегами (рис. 2).
Рис. 2. Реализация сразу нескольких интерфейсов
Для "привязки" интерфейсов к классу используется ключевое слово implements:
Теперь каждый класс, который расширяет интерфейсы Seo или Tаg, обязан реализовать абстрактные методы, заданные на уровне интерфейса, в противном случае РНР выдаст сообщение об ошибке.
Интерфейс рассматривается как абстрактный класс. Проверить, реализует ли текущей класс интерфейс, можно при помощи оператора instanceof, коrорый мы рассмотрели в предыдущей работе.
Наследование интерфейсов
Интерфейсы, так же как и обычные классы, могут наследовать друг друга. Например, от интерфейса Tag можно унаследовать интерфейс для списка авторов Author новостного материала (рис. 3).
Для наследования в интерфейсах, так же как и в классах, используется ключевое слово extends (листинг 1).
Рис. 3. Интерфейсы могут наследовать друг другу
Листинг 1 Наследование интерфейсов. Файл ifacemulti.php
Обратите внимание на одну деталь. Интерфейс Tаg расширяется интерфейсом Author, который, в свою очередь, реализуется классом News. Если попробовать расширить класс News интерфейсом Tаg напрямую, будет возвращена ошибка.