Несовместимость типов

Лабораторная работа №7. Наследование

При помощи механизма наследования вы можете создавать новые типы данных не "с нуля", а взяв за основу некоторый уже существующий класс, который в этом случае называют базовым (base class). nолучившийся же класс носит имя производного (derived class).

Наследование в ООП используется для нескольких различных целей.

Добавление в существующий класс новых методов и свойств или замена уже существующих. При этом "старая" версия класса уже не будет использоваться; ценность представляет именно новый, расширенный класс.

Наследование в целях классификации и обеспечения однотипности поведения различных классов. Дело в том, что новый, производный класс обладает теми же самыми "особенностями", что и базовый, и может использоваться везде вместо последнего. Например, рассмотрим базовый класс Автомобиль и производный от него - запорожец. Очевидно, что везде, где требуется объект типа Автомобиль, можно подставить и объект типа запорожец (но не наоборот). Создав еще несколько производных от Автомобиля классов (Мерседес, Таврия, жигули и т. д.), мы в ряде случаев можем работать с ними всеми однотипным образом, как с объектами типа Автомобиль, не вдаваясь в детали.

Расширение класса

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

Итак, пусть у нас есть некоторый класс FileLogger с определенными свойствами и методами. В листинге 1 приведен его код.

Листинг 1. Базовый класс. Файл File/Logger.php

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

Зададимся целью создать новый класс FileLoggerDebug, как бы "расширяющий" возможности класса FileLogger. Он будет добавлять ему несколько новых свойств и методов.

Метод включения

Добиться результата можно и без использования механизма наследования (листинг 2). Давайте посмотрим, как это сделать. Будем называть класс FileLogger "базовым" в кавычках, т. к. наследование в действительности не используется.

Листинг 2 Базовый класс. Файл File/Logger.php

Как видите, в этой реализации объект класса FileLoggerDebug0 содержит в своем составе подобъект класса FileLogger в качестве свойства. Это свойство лишь "частичка" объекта класса FileLogger􀄛bug0, не более того.

Недостатки метода

Опишем недостатки метода.

· Для каждого метода FileLogger мы должны явно создать в классе FileLoggerDebug0 функцию-посредника, которая делает лишь одну вещь: переадресует запрос объекту $this->logger. Минус этого способа огромен: при добавлении нового метода в FileLogger нам придется изменять и "производный" класс, чтобы он в нем появился. То же самое придется делать при удалении или переименовании метода из "базового" класса.

· Возникает проблема с наследованием свойств класса FileLogger. В нашем примере их, правда, нет, но в общем случае придется писать специальные методы _get () и _set () для перехвата обращений к несуществующим свойствам.

· Подобъект не "знает", что он в действительности не самостоятелен, а содержится в классе FileLoggerDebug0. В данном примере это и не важно, однако в реальных ситуациях базовый класс может использовать методы, определенные в производном! Такая техника называется использованием виртуальных методов.

· Мы не видим явно, что класс FileLoggerDebug0 лишь расширяет возможности FileLogger, а не является отдельной самостоятельной сущностью. Соответственно, мы не можем гарантировать, что везде, где допустимо использование объекта типа FileLogger, будет допустима и работа с FileLoggerDebug0.

· Мы должны обращаться к "части FileLogger" класса FileLoggerDebug0 через $logger->lоggеr->имяМетода (), а к членам самого класса FileLoggerDebug0 как $obj->имяМетода(). Последнее может быть довольно утомительным, если, как это часто бывает, в FileLoggerDebug0 будет существовать очень много методов из FileLogger и гораздо меньше - из самого FileLoggerDebug0. Кроме того, это заставляет нас постоянно помнить о внутреннем устройстве "производного" класса.

Пример использования созданного класса приведен в листинге 3.

Листинг 3 Проверка класса FileloggerDebug0. Файл inherit0.php

Как видим, "снаружи" все выглядит почти в точности так, будто бы в классе FileLogger появился новый метод – debug(), а старые сохранились. Сам класс при этом "переименовался" в FileLoggerDebug0.

Несовместимость типов

Вспомним теперь, что мы хотели получить расширение возможностей класса FileLogger, а не нечто, содержащее объекты FileLogger. Что означает "расширение"? Лишь одно: мы бы хотели, чтобы везде, где допустима работа с объектами класса FileLogger, была допустима и работа с объектами класса FileLoggerDebug0. Но в нашем примере это совсем не так. Например, попробуйте запустить скрипт, приведенный в листинге 4.

Листинг 4 Несовместимость типов. Файл inherit0cast.php

Вы увидите сообщение об ошибке:

Fatal error: Uncaught ТypeError: Argument 1 passed to croak() must bе an instance of FileLogger, instance of FileLoggerDebug0 given

Таким образом, РНР в момент вызова функции croak() не позволяет использовать FileLoggerDebug0 вместо FileLogger, хотя по логике такое применение вполне разумно.

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

Теперь рассмотрим, что же представляет собой "настоящее" наследование (или расширение) классов (листинг 5).

Листинг 5 Наследование. Файл File/Logger/Debug.php

Ключевое слово extends говорит о том, что создаваемый класс FileLoggerDebug является лишь "расширением" класса FileLogger, и не более того. То есть FileLoggerDebug содержит те же самые свойства и методы, что и FileLogger, но помимо них и еще некоторые дополнительные, "свои".

Теперь "часть FileLogger" находится прямо внутри класса FileLoggerDebug и может быть легко доступна, наравне с методами и свойствами самого класса FileLoggerDebug. Например, для объекта $logger класса FileLoggerDebug допустимы выражения $logger-> debug() и $logger-> log() без каких бы то ни было функций-посредников.

Итак, мы видим, что действительно класс FileLoggerDebug является воплощением идеи "расширение функциональности класса FileLogger". Обратите также внимание: мы можем теперь забыть, что FileLoggerDebug унаследовал от FileLogger некоторые свойства или методы - "снаружи" все выглядит так, будто класс в реализует их самостоятельно.


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



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