Класс наследование php

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

В PHP ООП наследование является фундаментальным принципом. Наследование влияет на то, как объекты соотносятся друг с другом. С его помощью мы можем создавать объекты, которые чем-то похожи, а в некоторых аспектах отличаются друг от друга.

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

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

Допустим, у нас есть класс с именем person . Он имеет несколько атрибутов или свойств. Теперь, если мы наследуем класс worker от базового класса person, мы можем сказать, что worker является person . worker будет иметь все свойства person, а также дополнительные свойства. person — это суперкласс, а worker — это подкласс. person и worker будут иметь общие характеристики и методы.

При определении класса worker мы наследуем его от класса person. В результате нам не нужно снова определять свойства и методы класса, все наследуется по умолчанию. Это экономит время, нам не приходится снова писать один и тот же код.

Вот ООП PHP пример:

В этом коде класс Person является базовым классом, а класс Worker — подклассом. Класс Worker может использовать метод getName() . Он был доступен в классе Person . Но так как класс Worker унаследовал его, то он может использовать этот метод.

Наследование является одним из способов, с помощью которых все свойства суперкласса наследуются подклассом, но свойства подклассов не наследуются в суперкласс. Наследование более ярко демонстрирует ООП PHP в действии. Так как это помогает расширить класс свойствами, унаследованными от родительского класса.

Переопределение функции при наследовании

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

Наследование в PHP и модификаторы доступа

В основах ООП PHP модификаторы доступа используются для контроля доступа из-за пределов класса к свойствам и методам-членам. Видимость данных и методов является очень важным аспектом, и для ограничения доступа к членам класса используются три модификатора доступа. Когда свойство или метод объявляются как статические, они могут быть вне конкретного класса.

Этот тип присваивается свойствам или методам по умолчанию, даже если вы не используете идентификатор доступа public перед именем. Свойство или метод, объявленные как public , доступны в классе, в котором они были объявлены, из любого другого класса или из любого из его подклассов. Следовательно, никаких ограничений на доступ к ним нет.

Когда член объявлен, как privat , он доступен только в классе, в котором он был объявлен. Он не доступен из подклассов или из-за пределов класса.

Защищенные члены

В ООП PHP , когда свойство или метод объявляются защищенными, это означает, что они доступны в классе, в котором они были объявлены, а также из его подклассов. Но они не доступны вне пределов этого класса. Любое свойство или метод могут быть защищены с помощью ключевого слова protected , размещенного перед его именем.

Перевод статьи « PHP inheritance » был подготовлен дружной командой проекта Сайтостроение от А до Я.

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

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

Прежде чем приступить к изучению синтаксиса наследования, давайте рассмотрим проблемы, которые оно поможет нам решить. Давайте вернемся к классу ShopProduct (который мы создали в предыдущей статье). В настоящее время он является достаточно обобщенным. С его помощью можно оперировать самыми разными товарами:

На выходе получаем следующее:

Вывод двух объектов различающихся семантически (по смыслу)

Как видим, разделение имени автора на две части пригодилось нам при работе и с книгами, и с компакт-дисками. В этом случае мы можем сортировать товары по фамилии автора (т.е. по полю, содержащему «Булгаков» и «Моральный кодекс»), а не по имени, в котором содержатся малозначимые «Михаил» и «Группа». Лень — это отличная стратегия проектирования, поэтому на данном этапе вам не следует переживать по поводу использования класса ShopProduct для более чем одного типа товара.

Но если в нашем примере добавить несколько новых требований, то все сразу усложнится. Представьте, например, что вам нужно отобразить данные, специфичные для книг и компакт-дисков. Скажем, для CD желательно вывести общее время звучания, а для книг — общее количество страниц. Конечно, могут быть и другие отличия, но эти хорошо иллюстрируют суть проблемы. Как расширить наш пример, чтобы учесть все эти изменения? На ум сразу приходят два варианта. Во-первых, можно поместить все данные в класс ShopProduct. Во-вторых, можно разбить ShopProduct на два отдельных класса.

Давайте рассмотрим первый подход. Итак, мы объединяем данные о книгах и компакт-дисках в одном классе:

Чтобы продемонстрировать большой объем выполняемой работы по кодированию, в данном примере были использованы методы доступа к свойствам $numPages и $playLength. В результате объект, экземпляр которого создается с помощью такого класса, будет всегда содержать избыточные методы. Кроме того, для CD экземпляр объекта нужно создавать с помощью бессмысленного аргумента конструктора. Таким образом, для CD будет сохраняться информация и функциональные возможности класса, относящиеся к книгам (общее количество страниц), а для книг — данные о времени звучания CD. Вероятно, пока вы можете с этим смириться. Но что будет, если мы добавим больше типов товаров, причем каждый — с собственными методами, а затем добавим больше методов для каждого типа? Наш класс будет становиться все более сложным и трудным для использования.

Поэтому принудительное объединение полей, относящихся к разным товарам, в один класс приведет к созданию слишком громоздких объектов с лишними свойствами и методами. Но этим проблемы не ограничиваются. С функциональностью тоже возникнут трудности. Представьте метод, который выводит краткую информацию о товаре. Скажем, отделу продаж нужна информация о товаре в виде одной строки для использования в счете-фактуре. Они хотят, чтобы мы включили в нее время звучания для компакт-диска и количество страниц для книг. Таким образом, при реализации этого метода нам придется учитывать тип каждого товара. Для отслеживания формата объекта можно использовать специальный флаг. Приведем пример:

Как видите, чтобы правильно установить значение свойства $type, нам нужно в конструкторе проверить значение аргумента $numPages. И снова класс ShopProduct стал более сложным, чем нужно. По мере добавления дополнительных отличий в форматы или новых форматов, нам будет трудно справляться с реализацией этого метода. Поэтому, видимо, для решения данной задачи необходимо применить второй подход.

Поскольку ShopProduct начинает напоминать «два класса в одном», мы должны это признать и создать два типа вместо одного. Однако если мы будем создавать два отдельных класса (например, CDProduct и BookProduct) то возникнет чрезмерное дублирование, т.к. в обоих классах содержаться некоторые одинаковые свойства и методы. Нам нужно создать общую функциональность в одном месте, чтобы избежать дублирования, но в то же время сделать так, чтобы при вызове метода, выводящего краткую информацию о товаре, учитывались особенности форматирования. Одним словом, нам необходимо использовать наследование.

Первый шаг в построении дерева наследования — найти элементы базового класса, которые не соответствуют друг другу или которыми нужно оперировать иначе. Мы знаем, что методы getPlayLength() и getNumberOfPages() противоречат друг другу. Нам также известно, что нужно создать разные реализации метода getSummaryLine(). Давайте используем эти различия как основу для создания двух производных классов:

Чтобы создать дочерний класс, необходимо использовать в объявлении класса ключевое слово extends. В данном примере мы создали два новых класса, BookProduct и CDProduct. Оба они расширяют класс ShopProduct.

Поскольку в производных классах конструкторы не определяются, при создании экземпляров объектов этих классов будет автоматически вызываться конструктор родительского класса. Дочерние классы наследуют доступ ко всем методам типа public и protected родительского класса (но не к методам и свойствам типа private). Это означает, что мы можем вызвать метод getProducer() для экземпляра объекта класса CDProduct, хотя метод getProducer() определен в классе ShopProduct:

Таким образом, оба наших дочерних класса наследуют поведение общего родительского класса. И мы можем обращаться с объектом BookProduct так, как будто это объект типа ShopProduct. Обратите внимание на то, что для обеспечения собственной реализации в обоих классах CDProduct и BookProduct переопределяется метод getSummaryLine(). Производные классы могут расширять и изменять функциональность родительских классов. И в то же время каждый класс наследует свойства родительского класса.

Реализация этого метода в Суперклассе может показаться избыточной, поскольку метод переопределяется в обоих дочерних классах. Тем не менее мы предоставляем базовый набор функциональных возможностей, который можно будет использовать в новом дочернем классе. Наличие этого метода в суперклассе также гарантирует для клиентского кода, что во всех объектах типа ShopProduct будет присутствовать метод getSummaryLine(). Позже вы увидите, как можно выполнить это требование в базовом классе, не предоставляя никакой его реализации. Каждый дочерний объект класса ShopProduct унаследует все свойства своего родителя. В собственных реализациях метода getSummaryLine() для обоих классов CDProduct и BookProduct обеспечивается доступ к свойству $title.

С понятием наследования сразу разобраться непросто. Определяя класс, который расширяет другой класс, мы гарантируем, что экземпляр его объекта определяется характеристиками сначала дочернего, а затем — родительского класса. Чтобы понять это, нужно размышлять с точки зрения поиска. При вызове $product2->getProducer() интерпретатор PHP не может найти такой метод в классе CDProduct. Поиск заканчивается неудачей, и поэтому используется стандартная реализация этого метода, заданная в классе ShopProduct. С другой стороны, когда мы вызываем $product2->getSummaryLine(), то интерпретатор PHP находит реализацию метода getSummaryLine() в классе CDProduct и вызывает его.

То же самое верно и в отношении доступа к свойствам. При обращении к свойству $title в методе getSummaryLine() из класса BookProduct, интерпретатор PHP не находит определение этого свойства в классе BookProduct. Поэтому он использует определение данного свойства, заданное в родительском классе ShopProduct. Поскольку свойство $title используется в обоих подклассах, следовательно, оно должно определяться в суперклассе.

Даже поверхностного взгляда на конструктор ShopProduct достаточно, чтобы понять, что в базовом классе по-прежнему выполняется доступ к тем данным, которыми должен оперировать дочерний класс. Так, конструктору класса BookProduct должен передаваться аргумент $numPages, значение которого заносится в одноименное свойство, а конструктор класса CDProduct должен обрабатывать аргумент и свойство $playLength. Чтобы добиться этого, мы определим методы конструктора в каждом дочернем классе.

Конструкторы и наследование

При определении конструктора в дочернем классе вы берете на себя ответственность за передачу требуемых аргументов родительскому классу. Если же вы этого не сделаете, то у вас получится частично сконструированный объект. Чтобы вызвать нужный метод из родительского класса, вам понадобится обратиться к самому этому классу через дескриптор. Для этой цели в PHP предусмотрено ключевое слово parent.

Смотрите так же:  Жалоба на отказ в рассрочке исполнения решения суда

Чтобы обратиться к методу в контексте класса, а не объекта, следует использовать символы «::», а не «->». Поэтому конструкция parent::__construct() означает следующее: «Вызвать метод __construct() родительского класса». Давайте изменим наш пример так, чтобы каждый класс оперировал только теми данными, которые имеют к нему отношение:

Каждый дочерний класс вызывает конструктор своего родительского класса, прежде чем определять собственные свойства. Базовый класс теперь «знает» только о собственных данных. Дочерние классы — это обычно «специализации» родительских классов. Как правило, следует избегать того, чтобы давать родительским классам какие-либо особые «знания» о дочерних классах. В случае изменения иерархии классов это часто приводило к проблемам. Множество ошибок возникало из-за того, что программисты, после непосредственного изменения «родителя» класса, забывали обновить сам конструктор. А при использовании унифицированного конструктора вызов родительского конструктора parent::__construct() означает обращение непосредственно к родительскому классу, независимо от того, какие изменения произошли в иерархии классов. Но, конечно, нужно позаботиться о том, чтобы этому родительскому классу были переданы правильные аргументы!

Вызов переопределенного метода

Ключевое слово parent можно использовать в любом методе, который переопределяет свой эквивалент в родительском классе. Когда мы переопределяем метод, то, возможно, хотим не удалить функции «родителя», а, скорее, расширить их. Достичь этого можно, вызвав метод родительского класса в контексте текущего объекта. Если вы снова посмотрите на реализации метода getSummaryLine(), то увидите, что значительная часть кода в них дублируется. И лучше этим воспользоваться, чем воспроизводить функциональность, уже разработанную в классе ShopProduct:

Мы определили основные функции для метода getSummaryLine() в базовом классе ShopProduct. Вместо того чтобы воспроизводить их в подклассах CDProduct и BookProduct, мы просто вызовем родительский метод, прежде чем добавлять дополнительные данные к итоговой строке.

Теперь, когда мы познакомились с основами наследования, можно, наконец, рассмотреть вопрос видимости свойств и методов в свете полной картины происходящего.

Модификаторы Public, Private и Protected: управление доступом к классам

До сих пор мы явно или неявно объявляли все свойства как public (общедоступные). Такой тип доступа задан по умолчанию для всех методов, а также свойств, объявленных с использованием устаревшего ключевого слова var. Элементы класса можно объявить как public (общедоступные), private (закрытые) или protected (защищенные). Ниже описана разница между ними:

К общедоступным свойствам и методам можно получать доступ из любого контекста.

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

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

Чем это может быть нам полезно? Ключевые слова, определяющие область видимости, позволяют показывать только те аспекты класса, которые требуются клиенту. Это позволяет создать ясный и понятный интерфейс для объекта.

Контроль доступа, позволяющий запрещать клиенту доступ к некоторым свойствам, поможет также избежать ошибок в коде. Предположим, мы хотим сделать так, чтобы в объектах типа ShopProduct поддерживались скидки. Для этого можно добавить свойство $discount и метод setDiscount():

Но тут у нас есть проблема. Мы хотим показать всем только скорректированную цену, но клиент может легко обойти метод getPrice() (если определить такой метод) и получить доступ к свойству $price:

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

Но определение свойств как private — не всегда удачная стратегия, поскольку тогда дочерний класс не сможет получить доступ к закрытым свойствам. А теперь представьте, что правила вашего бизнеса таковы: при покупке только книг скидку на них делать нельзя. Мы можем переопределить метод getPrice(), чтобы он возвращал свойство $price без применения скидки:

Поскольку свойство $price объявлено в классе ShopProduct с модификатором private, а не в BookProduct, попытка в приведенном выше коде получить к нему доступ закончится неудачей. Чтобы решить эту проблему, нужно объявить свойство $price защищенным (protected) и тем самым предоставить доступ к нему дочерним классам. Помните, что к защищенному свойству или методу нельзя получить доступ из-за пределов иерархии того класса, в котором это свойство или метод были объявлены. Доступ к ним можно получить только из исходного класса или его дочерних классов.

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

Методы как средство доступа к свойствам

Даже если в клиентской программе нужно будет работать со значениями, хранящимися в экземпляре вашего класса, как правило, стоит запретить прямой доступ к свойствам этого объекта. Вместо этого создайте методы, которые возвращают или устанавливают нужные значения. Такие методы называют методами доступа (accessors) или получателями (getter) и установщиками (setter).

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

В предыдущей статье мы приводили пример класса ShopProductWriter, с помощью которого выводилась информация об объектах типа ShopProduct. Давайте попробуем пойти дальше и сделать так, чтобы класс ShopProductWriter мог выводить информацию о любом количестве объектов типа ShopProduct одновременно:

Код PHP Вывод информации о всех объектах класса ShopProduct с помощью вспомогательного класса ShopProductWriter

Теперь класс ShopProductWriter стал намного полезнее. Он может содержать много объектов типа ShopProduct и сразу выводить информацию обо всех их. Но мы все еще должны полагаться на то, что программисты клиентского кода будут строго придерживаться правил работы с классом. Хотя мы предоставили метод addProduct(), мы не запретили программистам непосредственно выполнять операции над свойством $products. В результате можно не только добавить объект неправильного типа к массиву свойств $products, но и затереть весь массив и заменить его значением элементарного типа. Чтобы не допустить этого, нужно сделать свойство $products закрытым:

Теперь внешний код не сможет повредить массив свойств $products. Весь доступ к нему должен осуществляться через метод addProduct(), а уточнения типа класса, которые используются в объявлении этого метода, гарантируют, что к массиву свойств могут быть добавлены только объекты типа ShopProduct.

И в заключение давайте изменим класс ShopProduct и его дочерние классы так, чтобы ограничить доступ к свойствам:

В этой версии семейства классов ShopProduct нет ничего существенно нового. Все методы были явно сделаны общедоступными, а все свойства теперь стали либо закрытыми, либо защищенными. И для завершенности мы добавили ряд методов доступа.

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

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

Класс, который получается в результате наследования от другого, называется подклассом. Эту связь обычно описывают с помощью терминов «родительский» и «дочерний». Дочерний класс происходит от родительского и наследует его характеристики: свойства и методы. Обычно в подклассе к функциональности родительского класса (который также называют суперклассом) добавляются новые функциональные возможности.

Чтобы создать подкласс, необходимо использовать в объявлении класса ключевое слово extends , и после него указать имя класса, от которого выполняется наследование:

Подкласс наследует доступ ко всем методам и свойствам родительского класса, так как они имеют тип public . Это означает, что для экземпляров класса my_Cat мы можем вызывать метод add_age() и обращаться к свойству $age не смотря на то, что они определены в классе cat . Также в приведенном примере подкласс не имеет своего конструктора. Если в подклассе не объявлен свой конструктор, то при создании экземпляров подкласса будет автоматически вызываться конструктор суперкласса.

Обратите внимание на то, что в подклассах могут переопределяться свойства и методы. Определяя подкласс, мы гарантируем, что его экземпляр определяется характеристиками сначала дочернего, а затем родительского класса. Чтобы лучше это понять рассмотрим пример:

При вызове $kitty->foo() интерпретатор PHP не может найти такой метод в классе my_Cat , поэтому используется реализация этого метода заданная в классе Cat . Однако в подклассе определено собственное свойство $age , поэтому при обращении к нему в методе $kitty->foo() , интерпретатор PHP находит это свойство в классе my_Cat и использует его.

Так как мы уже рассмотрели тему про указание типа аргументов, осталось сказать о том, что если в качестве типа указан родительский класс, то все потомки для метода будут так же доступны для использования, посмотрите на следующий пример:

Мы можем обращаться с экземпляром класса my_Cat так, как будто это объект типа Cat , т.е. мы можем передать объект типа my_Cat методу foo() класса Cat , и все будет работать, как надо.

Оператор parent

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

Чтобы вызвать нужный метод из родительского класса, вам понадобится обратиться к самому этому классу через дескриптор. Для этой цели в PHP предусмотрено ключевое слово parent . Оператор parent позволяет подклассам обращаться к методам (и конструкторам) родительского класса и дополнять их существующую функциональность. Чтобы обратиться к методу в контексте класса, используются символы » :: » (два двоеточия). Синтаксис оператора parent :

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

Когда в дочернем классе определяется свой конструктор, PHP не вызывает конструктор родительского класса автоматически. Это необходимо сделать вручную в конструкторе подкласса. Подкласс сначала в своем конструкторе вызывает конструктор своего родительского класса, передавая нужные аргументы для инициализации, исполняет его, а затем выполняется код, который реализует дополнительную функциональность, в данном случае инициализирует свойство подкласса.

Ключевое слово parent можно использовать не только в конструкторах, но и в любом другом методе, функциональность которого вы хотите расширить, достигнуть этого можно, вызвав метод родительского класса:

Здесь сначала вызывается метод getstr() из суперкласса, значение которого присваивается переменной, а после этого выполняется остальной код определенный в методе подкласса.

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

public, protected и private: управление доступом

До этого момента мы явно объявляли все свойства как public (общедоступные). И такой тип доступа задан по умолчанию для всех методов.

Элементы класса можно объявлять как public (общедоступные), protected (защищенные) и private (закрытые). Рассмотрим разницу между ними:

  • К public (общедоступным) свойствам и методам, можно получить доступ из любого контекста.
  • К protected (защищенным) свойствам и методам можно получить доступ либо из содержащего их класса, либо из его подкласса. Никакому внешнему коду доступ к ним не предоставляется.
  • Вы можете сделать данные класса недоступными для вызывающей программы с помощью ключевого слова private (закрытые). К таким свойствам и методам можно получить доступ только из того класса, в котором они объявлены. Даже подклассы данного класса не имеют доступа к таким данным.

public — открытый доступ:

private — доступ только из методов класса:

protected — защищенный доступ:

Модификатор protected с точки зрения вызывающей программы выглядит точно так же, как и private : он запрещает доступ к данным объекта извне. Однако в отличие от private он позволяет обращаться к данным не только из методов своего класса, но также и из методов подкласса.

Смотрите так же:  Сколько стоит налог за 150 лошадиных сил

Класс наследование php

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

Итак, пусть у нас есть некоторый класс A с определенными свойствами и методами. Но то, что этот класс делает, нас не совсем устраивает — например, пусть он выполняет большинство функций, по сути нам необходимых, но не реализует некоторых других. Зададимся целью создать новый класс B, как бы «расширяющий» возможности класса A, добавляющий ему несколько новых свойств и методов. Сделать это можно двумя принципиально различными способами. Первый выглядит примерно так:

Поясним: в этой реализации объект класса B содержит в своем составе подобъект класса A в качестве свойства. Это свойство — лишь «частичка» объекта класса B, не более того. Подобъект не «знает», что он в действительности не самостоятелен, а содержится в классе B, поэтому не может предпринимать никаких действий, специфичных для этого класса.
Мы хотели получить расширение возможностей класса A, а не нечто, содержащее объекты A. Что означает «расширение»? Лишь одно: мы бы хотели, чтобы везде, где допустима работа с объектами класса A, была допустима и работа с объектами класса B. Но в приведенном примере это совсем не так.

Итак, мы имеем некоторые проблемы:

1. Мы не видим явно, что класс B лишь расширяет возможности A, а не является отдельной сущностью;
2. Мы должны обращаться к «части A» класса B через $obj->a->TestA(), а к членам самого класса B как $obj->TestB(). Последнее может быть довольно утомительным, если, как это часто бывает, в B будет использоваться очень много методов из A и гораздо меньше — из B. Кроме того, это заставляет нас постоянно помнить о внутреннем устройстве класса B.

Теперь на практике рассмотрим, что же представляет собой наследование (или расширение возможностей) классов:

A ( параметры_для_A );
// инициализируем другие поля B
>

Ключевое слово extends говорит о том, что создаваемый класс является лишь «расширением» класса A, и не более того. То есть B содержит те же самые свойства и методы, что и A, но, помимо них и еще некоторые дополнительные, «свои».
Теперь «часть A» находится прямо внутри класса B и может быть легко доступна, наравне с методами и свойствами самого класса B. Например, для объекта $obj класса B допустимы выражения $obj->TestA() и $obj->TestB().

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

Немного о терминологии: родительский класс A принято называть базовым классом , а класс дочерний класс B — производным от A. Иногда базовый класс также называют суперклассом , а производный — подкласcом .

Рассмотрим еще один пример на PHP:

parent_funct (); // Выводит ‘Это родительская функция’
$ object -> child_funct (); // Выводит ‘Это дочерняя функция’
$ object -> test (); // Выводит ‘Это дочерний класс’
?>

Дочерний класс (подкласс) Child наследует все методы и свойства суперкласса Parent.

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

Наследование — это один из «трёх китов» ООП, который позволяет создавать дочерние классы на основе родительских (супер-классов), забрав у них свойства, методы и конструкторы. Хочется заметить, что наследование классов в PHP встречается только уже в серьёзных проектах. Вдобавок, новички наследование редко используют вообще, но тем не менее. Когда я делал свой движок для сайта (а он достаточно большой), то использовал наследование классов в PHP на полную катушку. И о том, как это делается, я и напишу в этой статье.

Давайте для начала создадим какой-нибудь несложный класс, например, класс, отвечающий за объект «Автомобиль» (в файле «car.php«):

x = $x;
$this->y = $y;
>
public function move ($x, $y) <
$this->sound();
echo «Движение автомобиля из координат ($this->x, $this->y) в координаты ($x, $y)
«;
$this->x = $x;
$this->y = $y;
>
public function sound() <
echo «Звук движения автомобиля
«;
>
>
?>

В классе «Car» мы определили два свойства, отвечающие за текущие координаты местоположения автомобиля. Создали конструктор, который позволяет назначить начальные координаты. Затем создали метод move(), позволяющий начать движение в координаты, переданные в параметрах метода. Внутри этого метода мы вызываем метод sound(), который запускает звук движения. Вот такой придуманный класс.

Теперь создадим класс, отвечающий за «Легковой автомобиль» (в файле «auto.php«):

sound();
echo «Движение легкового автомобиля из координат ($this->x, $this->y) в координаты ($x, $y)
«;
$this->x = $x;
$this->y = $y;
>
public function sound() <
echo «Звук движения легкового автомобиля
«;
>
>
?>

В самом начале мы подключаем наш класс «Car«. Затем мы начинаем создавать класс «Auto«, который является наследником для класса «Car» («class Auto extends Car«). Ввиду того, что класс «Auto» уже забирает все свойства, методы и конструкторы, то нам нет необходимости их описывать заново. Однако, методы move() и sound() должны иметь другую реализацию, поэтому мы пишем другой код этих методов.

И, напоследок, создадим скрипт, который создаст объект «Car» и «Auto» и воспользуемся их методами и свойствами:

Вначале всё просто: мы создаём объект «Car«, выводим его свойства, используем его метод move(). А вот затем мы создаём объект «Auto«. Ввиду того, что мы не определяли своего конструктора в этом классе, то он берётся из родительского. Затем выводим свойства, которые мы, кстати, также не определяли, и они тоже берутся из родительского класса. А потом используем метод move(). И, разумеется, берётся реализация не из класса «Car«, а из класса «Auto«, который отвечает у нас за легковой автомобиль.

Однако, данный пример является несовершенным, точнее, даже очень грязным, ввиду наличия дублирования (повторяющийся код). И логики в том, что у нас существует «Автомобиль» и «Легковой автомобиль» тоже нет. Вы можете представить себе «Автомобиль«? Уверен, что у каждого из Вас будут совершенно разные образы. А вот «Легковой автомобиль» — это уже более точное определение (хотя тоже далеко от окончательно точного). И в ООП для неточных, абстрактных понятий, существует возможность создавать абстрактные классы, о которых мы поговорим в следующей статье. И Вы увидите, что код данного примера станет на порядок чище.

Кстати, даю домашнее задание: создать класс, отвечающий за «Грузовой автомобиль» (по аналогии с «Легковым автомобилем«), и который будет наследоваться от класса «Автомобиль«.

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

Объектно-ориентированный PHP: работа с наследованием

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

В этом уроке мы поговорим о наследовании в ООП и о том, как оно работает в PHP. С помощью наследования можно сделать классы намного сильнее и гибче, а также сэкономить уйму времени на написание скриптов.

Мы рассмотрим следующее:

  • Концепцию наследования, и почему его полезно использовать;
  • Как один PHP класс может наследоваться от другого;
  • Как один из “детей” класса может перегружать функциональность методов своего “родителя”;
  • Работа с методами и классами final;
  • Использование абстрактных классов;
  • Работа с интерфейсами.

Готовы? Тогда вперед!

Как осуществляется наследование?

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

На заметку: классы-родители также называют базовыми классами или супер-классами. Классы-наследники, в свою очередь, можно назвать дочерними классами или подклассами.

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

К примеру, в веб-приложении форума есть класс Member, у которого есть методы createPost(), editProfile(), showProfile() и др. Так как администраторы форума также являются его членами, то вы можете создать класс Administrator — дочерний класса Member. Класс Administrator наследует все поля и методы класса Member, а значит, объект класса Administrator будет вести себя точно так же, как объект Member.

Вы можете расширить функциональность класса Administrator, добавив в него такие методы, как createForum(), deleteForm() и banMember(). А если хотите назначать роли еще и администраторам, то добавьте в данный дочерний класс поле, например, $adminLevel.

Действуя таким образом, вы не захламляете класс Member методами и полями, которые не подходят для обычных участников форума, а только для администраторов. Вам также не придется копировать и вставлять методы и поля из класса Member в дочерний класс. Так что наследование подойдет вам как нельзя лучше.

На заметку: вы можете создать столько дочерних классов, сколько вам понадобится, от одного единственного супер-класса, и в каждый из них можно добавлять всё новые и новые методы и поля.

Создание дочерних классов

Итак, как же создать класс, который станет наследником другого класса, в PHP? Для этого существует ключевое слово extends:

Мы создали класс ParentClass, а затем класса ChildClass, который наследуется от ParentClass. ChildClass наследует все поля и методы класса ParentClass, и в него также могут быть добавлены свои поля и методы.

А теперь — пример. Создадим класс Member для воображаемого веб-форума, а затем класс Administrator — дочерний от Member:

Как видите, класс Member содержит поле public $username и поле private $loggedIn, а также методы для входа-выхода из форума и метод для определения того, зашел ли пользователь или нет.

Затем мы добавили класс-наследника Administrator. Он наследует все поля и методы класса Member. Мы также добавили в него дополнительные методы:

  • createForum( $forumName ) для создания нового форума с названием $forumName;
  • banMember( $member ) для бана пользователя $member.

На заметку: естественно, эти методы ничего не делают, так как форум наш — воображаемый. В них на страницу просто выводятся какие-то сообщения.

Давайте посмотрим на наши классы в действии. Создадим по одному объекту обоих классов, а затем вызовем некоторые из их методов:

На странице отобразится:

Вот, как это работает:

  1. Сначала мы создали объект класса Member, задали имя пользователя “Fred”, залогинили его и отобразили на странице сообщение о том, что он вошел на форум.
  2. Затем создаем объект класса Administrator. Так как данный класс наследуется от Member, мы можем пользоваться всеми методами и полями этого класса для объектов класса Administrator. Мы даем имя администратору — Mary — и логиним ее, после чего отображаем сообщение о том, что она вошла.
  3. Теперь вызываем метод класса Administrator createForum(), передав в него название форума — “Teddy Bears”.
  4. Наконец, вызываем метод banMember() от объекта — админа, передав имя пользователя Fred.

В этом и заключается суть наследования в ООП. Дальше в уроке мы рассмотрим различные способы манипулирования наследованием, включая перегрузку, классы и методы final, абстрактные классы и интерфейсы.

Перегрузка родительских методов

Как вы уже увидели, при создании дочернего класса, он наследует все поля и методы своего родительского класса. Тем не менее, может возникнуть необходимость изменить функциональность методов супер-класса в дочернем классе.

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

Перегрузкой метода login() в дочернем классе, вы можете изменить данный метод по своему усмотрению.

Чтобы перегрузить метод супер-класса в дочернем классе, просто создайте в нем метод с таким же названием. Тогда при вызове метода для объектов дочернего класса, будет вызываться именно перегруженный метод, а не метод супер-класса:

Давайте перегрузим метод login() для класса Administrator так, чтобы в файл записывались логи:

Смотрите так же:  Как отчиму оформить опекунство

Как видите, мы перегрузили метод login() класса Administrator, чтобы он отображал сообщения, как в файлах — логах.

Затем мы создали обычного пользователя (Fred) и администратора (Mary). При вызове метода login() от Фреда вызывается метод Member::login(). А когда мы вызываем метод от администратора Mary, вызовется метод Administrator::login(), так как PHP видит, что мы перегрузили этот метод для данного класса. На странице отобразится строка «Log entry: Mary logged in».

С другой стороны, мы не перегрузили метод logout() в дочернем классе, поэтому Member:logout() вызывается и для админов и для обычных пользователей.

Вызов метода супер-класса из дочернего класса

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

В примере, приведенном в прошлом параграфе, мы перегрузили метод login(). Но мы продублировали часть метода Member::login() в Administrator::login():

Вместо того, чтобы дублировать код, лучше вызвать метод Member::login() из Administrator::login().

Чтобы получить доступ к методу супер-класса из класса дочернего, воспользуйтесь ключевым словом parent:

Давайте теперь перепишем метод login() в дочернем классе так, чтобы из него вызывался тот же метод из класса-родителя, а затем добавим в него что-то новое:

Это не только сокращает код, но также обеспечивает легкость его будущих корректировок. Если вам позже понадобится изменить способ, по которому логинится любой пользователь, вам потребуется подкорректировать метод login() только в классе Member, и в классе Administrator будет вызываться уже измененный метод.

Предотвращение наследования с помощью методов и классов final

В большинстве случаев, давать возможность классам расширять свою функциональность посредством наследования — хорошая практика. Это ведь часть силы ООП.

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

Чтобы запретить дочерним классам перегружать методы супер-класса, добавьте перед его описанием ключевое слово final. Например, вы можете запретить перегрузку метода login() класса Member по причинам усиления безопасности:

Если кто-то попытается наследовать класс и перегрузить данный метод:

… PHP выведет сообщение об ошибке:

Вы можете также запретить наследование от всего класса:

Любая попытка создать дочерний класс данного класса, не увенчается успехом:

На заметку: несмотря на то что это больше касается Java, нежели PHP, данная статья приводит преимущества использования методов и классов final.

Работа с абстрактными классами

Абстрактный класс — это такой класс, который не может быть реализован, то есть, вы не сможете создать объект класса, если он абстрактный. Вместо этого вы создаете дочерние классы от него и спокойно создаете объекты от этих дочерних классов. Абстрактные классы представляют собой шаблоны для создания классов.

Абстрактный класс содержит один или несколько абстрактных методов. Когда вы создаете абстрактный метод в абстрактном классе, вы не добавляете в этот метод ничего. Вместо этого он должен быть описан в любом дочернем классе.

На заметку: как только вы создали хотя бы один абстрактный метод в классе, вы должны объявить этот класс как абстрактный.

Когда от абстрактного класса наследуется обычный класс, он должен реализовать все абстрактные методы класса-родителя. В противном случае, PHP сгенерирует ошибку. Так, абстрактный класс создает “правила поведения” для своих дочерних классов.

На заметку: вы можете добавлять в абстрактный класс и не абстрактные методы. Они будут обыкновенным образом наследоваться дочерними классами.

Давайте рассмотрим пример. Скажем, мы создаем веб-сайт, в котором есть как участники форума, так и покупатели онлай-магазина, который является частью нашего сайта. Так как и участники форума и покупатели — люди, мы можем создать абстрактный класс Person, в котором будут какие-то поля и методы, общие для всех пользователей сайта:

Как видите, мы создали абстрактный класс, добавив в его описание ключевое слово abstract. В этом классе есть несколько свойств, общих для всех людей, — $frstName и $lastName — а также методы для инициализации и чтения значений этих полей.

В классе также есть абстрактный метод showWelcomeMessage(). Этот метод выводит приветствие, когда пользователь входит на сайт. Опять же, мы добавляем ключевое слово abstract в описание данного метода, чтобы сделать его абстрактным. Так как он абстрактный, в нем нет ни строчки кода, это просто его объявление. Тем не менее, любой дочерний класс обязан добавить и описать метод showWelcomeMessage().

Теперь давайте создадим пару классов от абстрактного класса Person:

  1. класс Member для участников форума;
  2. класс Shopper для покупателей онлайн-магазина.

Как видите, каждый из них описывает метод showWelcomeMessage() из абстрактного супер-класса. Они имплементированы по-разному: в классе Member отображается сообщение «welcome to the forums», а в классе Shopper — «welcome to our online store», но это нормально. Главное то, что они оба описали данный метод.

Если бы один из них, например, Shopper, не описал метод, PHP выдал бы ошибку:

Наряду с имплементацией абстрактного метода, в каждом классе есть свои обычные методы. В Member есть метод newTopic() для создания новой темы в форуме, а в Shopper — метод addToCart() для добавления товаров в корзину.

Теперь мы можем создавать участников форума и покупателей на нашем сайте. Мы можем вызывать методы newTopic() и addToCart() от этих объектов, а также getName() и setName(), так как они наследуются от супер-класса Person.

Более того, зная, что классы Member и Shopper наследуются от Person, мы можем спокойно вызывать метод showWelcomeMessage() для обоих классов, так как он точно реализован и в том и в другом. Мы в этом уверены, так как знаем, что он был объявлен как абстрактный метод в классе Person.

На странице отобразится:

Создание и использование интерфейсов

Интерфейсы во многом схожи с абстрактными классами. Интерфейс — это шаблон, который задает поведение одного или более классов.

Вот основные отличия между интерфейсами и абстрактными классами:

  1. Ни один метод не может быть описан в интерфейсе. Они все абстрактны. В абстрактном классе могут быть и не абстрактные методы.
  2. Интерфейс не может содержать полей — только методы.
  3. Класс имплементирует интерфейс, и класс наследует или расширяет другой класс.
  4. Класс может имплементировать несколько интерфейсов одновременно. Этот же класс может наследовать другой класс. Но у дочернего класса может быть только один супер-класс (абстрактный или нет).

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

Чтобы создать класс, который имплементирует тот или иной интерфейс, напишите так:

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

Например, веб-форум может содержать класс Member для участников форума и класс Topic для тем, создаваемых участниками форума. В отношении наследования, эти классы скорее всего не будут зависеть друг от друга, так как они выполняют совершенно разные функции.

Тем не менее, давайте предположим, что нам нужно будет доставать их и записывать в базу данных как объекты класса Member, так и объекты Topic. Для этого мы создадим интерфейс Persistable, в котором будут методы для сохранения объектов в БД и извлечения их оттуда:

Теперь давайте создадим класс Member и имплементируем для него интерфейс Persistable. Это значит, что в интерфейсе должны быть методы save(), load() и delete():

Наш класс Topic также будет имплементировать данный интерфейс, поэтому в нем тоже должны быть эти три метода:

На заметку: так как у нас форум — воображаемый, вместо взаимодействия с базой данных в методах save(), load() и delete() просто выводятся сообщения.

Мы также добавили несколько дополнительных методов, например, Member::getUsername() для получения имени пользователя, и Topic::showHeader() для отображения названия темы, имени автора и времени создания темы.

Теперь можем создать объекты классов Member и Topic, а затем вызвать их методы getUsername() и showHeader(). Более того, зная, что эти классы имплементируют интерфейс Persistable, мы можем вызывать такие методы, как save(), load() или delete():

На странице отобразится:

Как видите, интерфейсы могут соединить классы, которые никак не относятся друг к другу, для определенных целей, например, записи в базу данных объектов класса. Не забудьте, что один класс может имплементировать несколько интерфейсов:

На заметку: интерфейсы — это мощное свойство ООП, и о них можно еще много чего сказать. Узнать о них больше можно в PHP документации.

В этом уроке вы ознакомились с одним из самых мощных свойств ООП — наследованием. Вы узнали:

  1. Как работает наследование, и как его использовать для расширения классов;
  2. Как создавать дочерние классы в PHP;
  3. Почему вам может понадобиться перегружать методы в дочерних классах;
  4. Как получить доступ к методам супер-класса;
  5. Всё о методах и классах final, и почему полезно их использовать;
  6. Концепцию абстрактных классов для создания шаблонов дочерних классов;
  7. Как использовать интерфейсы, чтобы задать общую функциональность несвзянным между собой классам.

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

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

А пока удачного кодирования!

Данный урок подготовлен для вас командой сайта ruseller.com
Источник урока: www.elated.com/articles/object-oriented-php-working-with-inheritance/
Перевел: Станислав Протасевич
Урок создан: 30 Июня 2011
Просмотров: 64089
Правила перепечатки

5 последних уроков рубрики «PHP»

Фильтрация данных с помощью zend-filter

Когда речь идёт о безопасности веб-сайта, то фраза «фильтруйте всё, экранируйте всё» всегда будет актуальна. Сегодня поговорим о фильтрации данных.

Контекстное экранирование с помощью zend-escaper

Обеспечение безопасности веб-сайта — это не только защита от SQL инъекций, но и протекция от межсайтового скриптинга (XSS), межсайтовой подделки запросов (CSRF) и от других видов атак. В частности, вам нужно очень осторожно подходить к формированию HTML, CSS и JavaScript кода.

Подключение Zend модулей к Expressive

Expressive 2 поддерживает возможность подключения других ZF компонент по специальной схеме. Не всем нравится данное решение. В этой статье мы расскажем как улучшили процесс подключение нескольких модулей.

Совет: отправка информации в Google Analytics через API

Предположим, что вам необходимо отправить какую-то информацию в Google Analytics из серверного скрипта. Как это сделать. Ответ в этой заметке.

Подборка PHP песочниц

Подборка из нескольких видов PHP песочниц. На некоторых вы в режиме online сможете потестить свой код, но есть так же решения, которые можно внедрить на свой сайт.

Возможно Вас заинтересует:

  • Конструкт 2 лицензия Конструкт 2 лицензия scirra.com посмотри я так понимаю если не выходить за рамки 5к баксов прибыли, то достаточно Personal лицензии Construct 2 Free Edition - Может быть загружена и установлена ​​на любой компьютер бесплатно, в том числе на […]
  • Как оформить обратную связь на сайте Контактная форма для сайта: способы установки Трудно сейчас представить сайт без формы для обратной связи. Поэтому если на вашем ресурсе контактного раздела нет — стоит его установить. Разберемся, как это сделать. Решения для организации обратной […]
  • Как оформить рнр Как вставить PHP-код в HTML З ачем нужна вставка PHP-кода в HTML вообще? Что это даёт? И как осуществляется? П ричин для использования PHP в сайтах на HTML может быть множество. Я же рассмотрю одну. Именно она побудила меня «залезть» в PHP и найти […]
  • Asterisk требования к серверу Asterisk требования к серверу Процессор и оперативная память - ключевые характеристики сервера IP телефонии. Для небольшого офиса (до 10 сотрудников) будет достаточно процессора 1 ГГЦ и памяти 2 Гб. Если одновременных звонков будет много , […]
  • Развод родителей для ребенка 8 лет Развод родителей глазами ребенка Что чувствует ребенок, когда его папа и мама расстаются? Какими он видит своих близких людей, болезненно переживающих разрыв отношений? Трогательная шестиминутная история о мальчике, чьи родители после развода […]
  • Адвокат свиридова Адвокат Свиридова Екатерина Николаевна Полезная информация? Поделиться: Отзывы об адвокате Вы можете оставить отзыв об адвокате — указывайте больше фактов (время, имена, номера дел в судах). Короткие отзывы вида "Хороший адвокат" не информативны и […]

Author: admin