Объектно-ориентированный PHP: работа с наследованием
Добро пожаловать в третий урок из серии, посвященной ООП. Если вы не проходили первые два урока, то возможно, захотите с ними ознакомиться, так как данный урок базируется на них:
В этом уроке мы поговорим о наследовании в ООП и о том, как оно работает в PHP. С помощью наследования можно сделать классы намного сильнее и гибче, а также сэкономить уйму времени на написание скриптов.
Мы рассмотрим следующее:
- Концепцию наследования, и почему его полезно использовать;
- Как один PHP класс может наследоваться от другого;
- Как один из “детей” класса может перегружать функциональность методов своего “родителя”;
- Работа с методами и классами final;
- Использование абстрактных классов;
- Работа с интерфейсами.
Готовы? Тогда вперед!
Как осуществляется наследование?
Наследование базируется на понятиях класс-родитель и класс-наследник. Используя определенный синтаксис, вы можете создать класс, который будет наследовать другой класс (становится его наследником).
На заметку: классы-родители также называют базовыми классами или супер-классами. Классы-наследники, в свою очередь, можно назвать дочерними классами или подклассами.
Когда вы создаете дочерний класс, он наследует все поля и методы своего базового класса. В дочерний класс можно добавлять дополнительные поля и методы, тем самым расширяя функциональность класса-родителя.
К примеру, в веб-приложении форума есть класс Member, у которого есть методы createPost(), editProfile(), showProfile() и др. Так как администраторы форума также являются его членами, то вы можете создать класс Administrator - дочерний класса Member. Класс Administrator наследует все поля и методы класса Member, а значит, объект класса Administrator будет вести себя точно так же, как объект Member.
Вы можете расширить функциональность класса Administrator, добавив в него такие методы, как createForum(), deleteForm() и banMember(). А если хотите назначать роли еще и администраторам, то добавьте в данный дочерний класс поле, например, $adminLevel.
Действуя таким образом, вы не захламляете класс Member методами и полями, которые не подходят для обычных участников форума, а только для администраторов. Вам также не придется копировать и вставлять методы и поля из класса Member в дочерний класс. Так что наследование подойдет вам как нельзя лучше.
На заметку: вы можете создать столько дочерних классов, сколько вам понадобится, от одного единственного супер-класса, и в каждый из них можно добавлять всё новые и новые методы и поля.
Создание дочерних классов
Итак, как же создать класс, который станет наследником другого класса, в PHP? Для этого существует ключевое слово extends:
class ParentClass { // описание полей и методов } class ChildClass extends ParentClass { // описание дополнительных полей и методов }
Мы создали класс ParentClass, а затем класса ChildClass, который наследуется от ParentClass. ChildClass наследует все поля и методы класса ParentClass, и в него также могут быть добавлены свои поля и методы.
А теперь - пример. Создадим класс Member для воображаемого веб-форума, а затем класс Administrator - дочерний от Member:
class Member { public $username = ""; private $loggedIn = false; public function login() { $this->loggedIn = true; } public function logout() { $this->loggedIn = false; } public function isLoggedIn() { return $this->loggedIn; } } class Administrator extends Member { public function createForum( $forumName ) { echo "$this->username created a new forum: $forumName<br>"; } public function banMember( $member ) { echo "$this->username banned the member: $member->username<br>"; } }
Как видите, класс Member содержит поле public $username и поле private $loggedIn, а также методы для входа-выхода из форума и метод для определения того, зашел ли пользователь или нет.
Затем мы добавили класс-наследника Administrator. Он наследует все поля и методы класса Member. Мы также добавили в него дополнительные методы:
- createForum( $forumName ) для создания нового форума с названием $forumName;
- banMember( $member ) для бана пользователя $member.
На заметку: естественно, эти методы ничего не делают, так как форум наш - воображаемый. В них на страницу просто выводятся какие-то сообщения.
Давайте посмотрим на наши классы в действии. Создадим по одному объекту обоих классов, а затем вызовем некоторые из их методов:
// создаем участника форума и логиним его $member = new Member(); $member->username = "Fred"; $member->login(); echo $member->username . " is " . ( $member->isLoggedIn() ? "logged in" : "logged out" ) . "<br>"; // создаем администратора и логиним его $admin = new Administrator(); $admin->username = "Mary"; $admin->login(); echo $admin->username . " is " . ( $member->isLoggedIn() ? "logged in" : "logged out" ) . "<br>"; // отобразит "Mary created a new forum: Teddy Bears" $admin->createForum( "Teddy Bears" ); // отобразит "Mary banned the member: Fred" $admin->banMember( $member );
На странице отобразится:
Fred is logged in Mary is logged in Mary created a new forum: Teddy Bears Mary banned the member: Fred
Вот, как это работает:
- Сначала мы создали объект класса Member, задали имя пользователя “Fred”, залогинили его и отобразили на странице сообщение о том, что он вошел на форум.
- Затем создаем объект класса Administrator. Так как данный класс наследуется от Member, мы можем пользоваться всеми методами и полями этого класса для объектов класса Administrator. Мы даем имя администратору - Mary - и логиним ее, после чего отображаем сообщение о том, что она вошла.
- Теперь вызываем метод класса Administrator createForum(), передав в него название форума - “Teddy Bears”.
- Наконец, вызываем метод banMember() от объекта - админа, передав имя пользователя Fred.
В этом и заключается суть наследования в ООП. Дальше в уроке мы рассмотрим различные способы манипулирования наследованием, включая перегрузку, классы и методы final, абстрактные классы и интерфейсы.
Перегрузка родительских методов
Как вы уже увидели, при создании дочернего класса, он наследует все поля и методы своего родительского класса. Тем не менее, может возникнуть необходимость изменить функциональность методов супер-класса в дочернем классе.
На примере с форумом: когда админ логинится, это происходит абсолютно так же, как и для обычного пользователя, но вы, возможно, захотите записывать логи в определенный файл, в целях безопасности.
Перегрузкой метода login() в дочернем классе, вы можете изменить данный метод по своему усмотрению.
Чтобы перегрузить метод супер-класса в дочернем классе, просто создайте в нем метод с таким же названием. Тогда при вызове метода для объектов дочернего класса, будет вызываться именно перегруженный метод, а не метод супер-класса:
class ParentClass { public function myMethod() { // (действия) } } class ChildClass extends ParentClass { public function myMethod() { // вызывется для объекта класса ChildClass // вместо метода супер-класса MyMethod() } }
Давайте перегрузим метод login() для класса Administrator так, чтобы в файл записывались логи:
class Member { public $username = ""; private $loggedIn = false; public function login() { $this->loggedIn = true; } public function logout() { $this->loggedIn = false; } } class Administrator extends Member { public function login() { $this->loggedIn = true; echo "Log entry: $this->username logged in<br>"; } } // создаем нового пользователя и логиним его $member = new Member(); $member->username = "Fred"; $member->login(); $member->logout(); // создаем нового администратора и логиним его $admin = new Administrator(); $admin->username = "Mary"; $admin->login(); // отобразит "Log entry: Mary logged in" $admin->logout();
Как видите, мы перегрузили метод login() класса Administrator, чтобы он отображал сообщения, как в файлах - логах.
Затем мы создали обычного пользователя (Fred) и администратора (Mary). При вызове метода login() от Фреда вызывается метод Member::login(). А когда мы вызываем метод от администратора Mary, вызовется метод Administrator::login(), так как PHP видит, что мы перегрузили этот метод для данного класса. На странице отобразится строка "Log entry: Mary logged in".
С другой стороны, мы не перегрузили метод logout() в дочернем классе, поэтому Member:logout() вызывается и для админов и для обычных пользователей.
Вызов метода супер-класса из дочернего класса
Когда вы перегружаете метод в дочернем классе, вам обычно не требуется изменять его полностью. Вам нужно будет использовать функциональность метода супер-класса и добавить кое-что в метод дочернего класса.
В примере, приведенном в прошлом параграфе, мы перегрузили метод login(). Но мы продублировали часть метода Member::login() в Administrator::login():
class Administrator extends Member { public function login() { $this->loggedIn = true; echo "Log entry: $this->username logged in<br>"; } }
Вместо того, чтобы дублировать код, лучше вызвать метод Member::login() из Administrator::login().
Чтобы получить доступ к методу супер-класса из класса дочернего, воспользуйтесь ключевым словом parent:
Давайте теперь перепишем метод login() в дочернем классе так, чтобы из него вызывался тот же метод из класса-родителя, а затем добавим в него что-то новое:
class Administrator extends Member { public function login() { parent::login(); echo "Log entry: $this->username logged in<br>"; } }
Это не только сокращает код, но также обеспечивает легкость его будущих корректировок. Если вам позже понадобится изменить способ, по которому логинится любой пользователь, вам потребуется подкорректировать метод login() только в классе Member, и в классе Administrator будет вызываться уже измененный метод.
Предотвращение наследования с помощью методов и классов final
В большинстве случаев, давать возможность классам расширять свою функциональность посредством наследования - хорошая практика. Это ведь часть силы ООП.
Однако бывает такое, что перегрузка методов супер-классов может привести к неполадкам, снизить безопасность, усложнить код. В этих случаях, было бы полезно запретить наследование метода или даже всего класса.
Чтобы запретить дочерним классам перегружать методы супер-класса, добавьте перед его описанием ключевое слово final. Например, вы можете запретить перегрузку метода login() класса Member по причинам усиления безопасности:
class Member { public $username = ""; private $loggedIn = false; public final function login() { $this->loggedIn = true; } public function logout() { $this->loggedIn = false; } public function isLoggedIn() { return $this->loggedIn; } }
Если кто-то попытается наследовать класс и перегрузить данный метод:
class NaughtyMember extends Member { public function login() { $this->loggedIn = true; // сделать что-то плохое } }
… PHP выведет сообщение об ошибке:
Fatal error: Cannot override final method Member::login()
Вы можете также запретить наследование от всего класса:
final class Member { // от этого класса нельзя вообще наследовать }
Любая попытка создать дочерний класс данного класса, не увенчается успехом:
Fatal error: Class NaughtyMember may not inherit from final class (Member)
На заметку: несмотря на то что это больше касается Java, нежели PHP, данная статья приводит преимущества использования методов и классов final.
Работа с абстрактными классами
Абстрактный класс - это такой класс, который не может быть реализован, то есть, вы не сможете создать объект класса, если он абстрактный. Вместо этого вы создаете дочерние классы от него и спокойно создаете объекты от этих дочерних классов. Абстрактные классы представляют собой шаблоны для создания классов.
Абстрактный класс содержит один или несколько абстрактных методов. Когда вы создаете абстрактный метод в абстрактном классе, вы не добавляете в этот метод ничего. Вместо этого он должен быть описан в любом дочернем классе.
На заметку: как только вы создали хотя бы один абстрактный метод в классе, вы должны объявить этот класс как абстрактный.
Когда от абстрактного класса наследуется обычный класс, он должен реализовать все абстрактные методы класса-родителя. В противном случае, PHP сгенерирует ошибку. Так, абстрактный класс создает “правила поведения” для своих дочерних классов.
На заметку: вы можете добавлять в абстрактный класс и не абстрактные методы. Они будут обыкновенным образом наследоваться дочерними классами.
Давайте рассмотрим пример. Скажем, мы создаем веб-сайт, в котором есть как участники форума, так и покупатели онлай-магазина, который является частью нашего сайта. Так как и участники форума и покупатели - люди, мы можем создать абстрактный класс Person, в котором будут какие-то поля и методы, общие для всех пользователей сайта:
abstract class Person { private $firstName = ""; private $lastName = ""; public function setName( $firstName, $lastName ) { $this->firstName = $firstName; $this->lastName = $lastName; } public function getName() { return "$this->firstName $this->lastName"; } abstract public function showWelcomeMessage(); }
Как видите, мы создали абстрактный класс, добавив в его описание ключевое слово abstract. В этом классе есть несколько свойств, общих для всех людей, - $frstName и $lastName - а также методы для инициализации и чтения значений этих полей.
В классе также есть абстрактный метод showWelcomeMessage(). Этот метод выводит приветствие, когда пользователь входит на сайт. Опять же, мы добавляем ключевое слово abstract в описание данного метода, чтобы сделать его абстрактным. Так как он абстрактный, в нем нет ни строчки кода, это просто его объявление. Тем не менее, любой дочерний класс обязан добавить и описать метод showWelcomeMessage().
Теперь давайте создадим пару классов от абстрактного класса Person:
- класс Member для участников форума;
- класс Shopper для покупателей онлайн-магазина.
class Member extends Person { public function showWelcomeMessage() { echo "Hi " . $this->getName() . ", welcome to the forums!<br>"; } public function newTopic( $subject ) { echo "Creating new topic: $subject<br>"; } } class Shopper extends Person { public function showWelcomeMessage() { echo "Hi " . $this->getName() . ", welcome to our online store!<br>"; } public function addToCart( $item ) { echo "Adding $item to cart<br>"; } }
Как видите, каждый из них описывает метод showWelcomeMessage() из абстрактного супер-класса. Они имплементированы по-разному: в классе Member отображается сообщение "welcome to the forums", а в классе Shopper - "welcome to our online store", но это нормально. Главное то, что они оба описали данный метод.
Если бы один из них, например, Shopper, не описал метод, PHP выдал бы ошибку:
Class Shopper contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Person::showWelcomeMessage)
Наряду с имплементацией абстрактного метода, в каждом классе есть свои обычные методы. В Member есть метод newTopic() для создания новой темы в форуме, а в Shopper - метод addToCart() для добавления товаров в корзину.
Теперь мы можем создавать участников форума и покупателей на нашем сайте. Мы можем вызывать методы newTopic() и addToCart() от этих объектов, а также getName() и setName(), так как они наследуются от супер-класса Person.
Более того, зная, что классы Member и Shopper наследуются от Person, мы можем спокойно вызывать метод showWelcomeMessage() для обоих классов, так как он точно реализован и в том и в другом. Мы в этом уверены, так как знаем, что он был объявлен как абстрактный метод в классе Person.
Вот пример:
$aMember = new Member(); $aMember->setName( "John", "Smith" ); $aMember->showWelcomeMessage(); $aMember->newTopic( "Teddy bears are great" ); $aShopper = new Shopper(); $aShopper->setName( "Mary", "Jones" ); $aShopper->showWelcomeMessage(); $aShopper->addToCart( "Ornate Table Lamp" );
На странице отобразится:
Hi John Smith, welcome to the forums! Creating new topic: Teddy bears are great Hi Mary Jones, welcome to our online store! Adding Ornate Table Lamp to cart
Создание и использование интерфейсов
Интерфейсы во многом схожи с абстрактными классами. Интерфейс - это шаблон, который задает поведение одного или более классов.
Вот основные отличия между интерфейсами и абстрактными классами:
- Ни один метод не может быть описан в интерфейсе. Они все абстрактны. В абстрактном классе могут быть и не абстрактные методы.
- Интерфейс не может содержать полей - только методы.
- Класс имплементирует интерфейс, и класс наследует или расширяет другой класс.
- Класс может имплементировать несколько интерфейсов одновременно. Этот же класс может наследовать другой класс. Но у дочернего класса может быть только один супер-класс (абстрактный или нет).
Как и абстрактный класс, интерфейс объявляет несколько методов, которые должны быть реализованы в любом классе, который имплементирует данный интерфейс. Синтаксис выглядит так:
interface MyInterface { public function aMethod(); public function anotherMethod(); }
Чтобы создать класс, который имплементирует тот или иной интерфейс, напишите так:
class MyClass implements MyInterface { public function aMethod() { // (имплементация метода) } public function anotherMethod() { // (имплементация метода) } }
Интерфейсы полезны в случаях, когда нужно создать несколько несвязанных между собой классов, у которых будет общая функциональность.
Например, веб-форум может содержать класс Member для участников форума и класс Topic для тем, создаваемых участниками форума. В отношении наследования, эти классы скорее всего не будут зависеть друг от друга, так как они выполняют совершенно разные функции.
Тем не менее, давайте предположим, что нам нужно будет доставать их и записывать в базу данных как объекты класса Member, так и объекты Topic. Для этого мы создадим интерфейс Persistable, в котором будут методы для сохранения объектов в БД и извлечения их оттуда:
interface Persistable { public function save(); public function load(); public function delete(); }
Теперь давайте создадим класс Member и имплементируем для него интерфейс Persistable. Это значит, что в интерфейсе должны быть методы save(), load() и delete():
class Member implements Persistable { private $username; private $location; private $homepage; public function __construct( $username, $location, $homepage ) { $this->username = $username; $this->location = $location; $this->homepage = $homepage; } public function getUsername() { return $this->username; } public function getLocation() { return $this->location; } public function getHomepage() { return $this->homepage; } public function save() { echo "Saving member to database<br>"; } public function load() { echo "Loading member from database<br>"; } public function delete () { echo "Deleting member from database<br>"; } }
Наш класс Topic также будет имплементировать данный интерфейс, поэтому в нем тоже должны быть эти три метода:
class Topic implements Persistable { private $subject; private $author; private $createdTime; public function __construct( $subject, $author ) { $this->subject = $subject; $this->author = $author; $this->createdTime = time(); } public function showHeader() { $createdTimeString = date( 'l jS M Y h:i:s A', $this->createdTime ); $authorName = $this->author->getUsername(); echo "$this->subject (created on $createdTimeString by $authorName)<br>"; } public function save() { echo "Saving topic to database<br>"; } public function load() { echo "Loading topic from database<br>"; } public function delete () { echo "Deleting topic from database<br>"; } }
На заметку: так как у нас форум - воображаемый, вместо взаимодействия с базой данных в методах save(), load() и delete() просто выводятся сообщения.
Мы также добавили несколько дополнительных методов, например, Member::getUsername() для получения имени пользователя, и Topic::showHeader() для отображения названия темы, имени автора и времени создания темы.
Теперь можем создать объекты классов Member и Topic, а затем вызвать их методы getUsername() и showHeader(). Более того, зная, что эти классы имплементируют интерфейс Persistable, мы можем вызывать такие методы, как save(), load() или delete():
$aMember = new Member( "fred", "Chicago", "http://example.com/" ); echo $aMember->getUsername() . " lives in " . $aMember->getLocation() ."<br>"; $aMember->save(); $aTopic = new Topic( "Teddy Bears are Great", $aMember ); $aTopic->showHeader(); $aTopic->save();
На странице отобразится:
fred lives in Chicago Saving member to database Teddy Bears are Great (created on Wednesday 25th May 2011 02:19:14 AM by fred) Saving topic to database
Как видите, интерфейсы могут соединить классы, которые никак не относятся друг к другу, для определенных целей, например, записи в базу данных объектов класса. Не забудьте, что один класс может имплементировать несколько интерфейсов:
class MyClass implements anInterface, anotherInterface { ... }
На заметку: интерфейсы - это мощное свойство ООП, и о них можно еще много чего сказать. Узнать о них больше можно в PHP документации.
Заключение
В этом уроке вы ознакомились с одним из самых мощных свойств ООП - наследованием. Вы узнали:
- Как работает наследование, и как его использовать для расширения классов;
- Как создавать дочерние классы в PHP;
- Почему вам может понадобиться перегружать методы в дочерних классах;
- Как получить доступ к методам супер-класса;
- Всё о методах и классах final, и почему полезно их использовать;
- Концепцию абстрактных классов для создания шаблонов дочерних классов;
- Как использовать интерфейсы, чтобы задать общую функциональность несвзянным между собой классам.
Если вы прошли все уроки из данной серии, то вы уже сможете писать сложные приложения на ООП. Поздравляю!
В следующем и последнем уроке я покажу вам супер-полезные ООП свойства, которые есть в PHP.
А пока удачного кодирования!
Источник: http://feedproxy.google.com/~r/ruseller/CdHX/~3/KboXyLK86Z4/lessons.php
Дайджест новых статей по интернет-маркетингу на ваш email
Новые статьи и публикации
- 2024-11-26 » Капитан грузового судна, или Как начать использовать Docker в своих проектах
- 2024-11-26 » Обеспечение безопасности ваших веб-приложений с помощью PHP OOP и PDO
- 2024-11-22 » Ошибки в Яндекс Вебмастере: как найти и исправить
- 2024-11-22 » Ошибки в Яндекс Вебмастере: как найти и исправить
- 2024-11-15 » Перенос сайта на WordPress с одного домена на другой
- 2024-11-08 » OSPanel 6: быстрый старт
- 2024-11-08 » Как установить PhpMyAdmin в Open Server Panel
- 2024-09-30 » Как быстро запустить Laravel на Windows
- 2024-09-25 » Next.js
- 2024-09-05 » OpenAI рассказал, как запретить ChatGPT использовать содержимое сайта для обучения
- 2024-08-28 » Чек-лист: как увеличить конверсию интернет-магазина на примере спортпита
- 2024-08-01 » WebSocket
- 2024-07-26 » Интеграция с Яндекс Еда
- 2024-07-26 » Интеграция с Эквайринг
- 2024-07-26 » Интеграция с СДЕК
- 2024-07-26 » Интеграция с Битрикс-24
- 2024-07-26 » Интеграция с Travelline
- 2024-07-26 » Интеграция с Iiko
- 2024-07-26 » Интеграция с Delivery Club
- 2024-07-26 » Интеграция с CRM
- 2024-07-26 » Интеграция с 1C-Бухгалтерия
- 2024-07-24 » Что такое сторителлинг: техники и примеры
- 2024-07-17 » Ошибка 404: что это такое и как ее использовать для бизнеса
- 2024-07-03 » Размещайте прайс-листы на FarPost.ru и продавайте товары быстро и выгодно
- 2024-07-01 » Профилирование кода в PHP
- 2024-06-28 » Изучаем ABC/XYZ-анализ: что это такое и какие решения с помощью него принимают
- 2024-06-17 » Зачем вам знать потребности клиента
- 2024-06-11 » Что нового в работе Яндекс Метрики: полный обзор обновления
- 2024-06-11 » Поведенческие факторы ранжирования в Яндексе
- 2024-06-11 » Скорость загрузки сайта: почему это важно и как влияет на ранжирование
Великие умы обсуждают идеи, средние - обсуждают поступки, а малые - людей Индийская пословица |
Мы создаем сайты, которые работают! Профессионально обслуживаем и продвигаем их , а также по всей России и ближнему зарубежью с 2006 года!
Как мы работаем
Заявка
Позвоните или оставьте заявку на сайте.
Консультация
Обсуждаем что именно Вам нужно и помогаем определить как это лучше сделать!
Договор
Заключаем договор на оказание услуг, в котором прописаны условия и обязанности обеих сторон.
Выполнение работ
Непосредственно оказание требующихся услуг и работ по вашему заданию.
Поддержка
Сдача выполненых работ, последующие корректировки и поддержка при необходимости.