
Reg.ru: домены и хостинг
Крупнейший регистратор и хостинг-провайдер в России.
Более 2 миллионов доменных имен на обслуживании.
Продвижение, почта для домена, решения для бизнеса.
Более 700 тыс. клиентов по всему миру уже сделали свой выбор.

Бесплатный Курс "Практика HTML5 и CSS3"
Освойте бесплатно пошаговый видеокурс
по основам адаптивной верстки
на HTML5 и CSS3 с полного нуля.

Фреймворк Bootstrap: быстрая адаптивная вёрстка
Пошаговый видеокурс по основам адаптивной верстки в фреймворке Bootstrap.
Научитесь верстать просто, быстро и качественно, используя мощный и практичный инструмент.
Верстайте на заказ и получайте деньги.
*Наведите курсор мыши для приостановки прокрутки.
Работа с наследованием в PHP
Перед изучением данной статьи вы можете прочитать предыдущую статью из этой серии - "Наследование в PHP".
Первый шаг в построении дерева наследования - найти элементы базового класса, которые не соответствуют друг другу или которыми нужно оперировать иначе.
Мы знаем, что методы getPlayLength() и getNumberOfPages() противоречат друг другу. Нам также известно, что нужно создавать разные реализации метода getSummaryLine().
Давайте используем эти различия как основу для создания двух производных классов.
class ShopProduct { public $numPages; public $playLength; public $title; public $producerMainName; public $producerFirstName; public $price; function __construct( $title, $firstName, $mainName, $price, $numPages=0, $playLength=0 ) { $this->title = $title; $this->producerFirstName = $firstName; $this->producerMainName = $mainName; $this->price = $price; $this->numPages = $numPages; $this->playLength = $playLength; } function getProducer() { return "{$this->producerFirstName}". " {$this->producerMainName}"; } function getSummaryLine() { $base = "$this->title ( $this->producerMainName, "; $base .= "$this->producerFirstName )"; return $base; } } class CdProduct extends ShopProduct { function getPlayLength() { return $this->playLength; } function getSummaryLine() { $base = "$this->title ( $this->producerMainName, "; $base .= "$this->producerFirstName )"; $base .= ": Время звучания - $this->playLength"; return $base; } } class BookProduct extends ShopProduct { function getNumberOfPages() { return $this->numPages; } function getSummaryLine() { $base = "$this->title ( $this->producerMainName, "; $base .= "$this->producerFirstName )"; $base .= ": страниц - $this->numPages"; return $base; } }
Чтобы создать дочерний класс, необходимо использовать в объявлении класса ключевое слово extends. В данном примере мы создали два новых класса - BookProduct и СdProduct. Оба они расширяют класс ShopProduct.
Поскольку в производных классах конструкторы не определяются, при создании экземпляров объектов этих классов будет автоматически вызываться конструктор родительского класса.
Дочерние классы наследуют доступ ко всем методам типа public и protected родительского класса (но не к методами и свойствам типа private).
Это означает, что мы можем вызвать метод getProducer() для экземпляра объекта класса CdProduct, хотя метод определен в классе ShopProduct.
$product2 = new CdProduct( "Пропавший без вести", "Группа", "ДДТ", 10.99, null, 60.33 ); print "Исполнитель: ".$product2->getProducer()."\n";
Таким образом, оба наших дочерних класса наследуют поведение общего родительского класса. И мы можем обращаться с объектом BookProduct так, как будто это объект типа ShopProduct.
Мы можем передать объект BookProduct или CdProduct методу write() класса ShopProductWriter, и все будет работать как надо.
Обратите внимание на то, что для обеспечения собственной реализации в обоих классах CdProduct и BookProduct переопределяется метод getSummaryLine(). Производные классы могут расширять и изменять функциональность родительских классов. И в то же время каждый класс наследует свойства родительского класса.
Реализация этого метода в суперклассе может показаться избыточной, поскольку метод переопределяется в обоих дочерних классах. Тем не менее, мы предоставляем базовый набор функциональных возможностей, который можно будет использовать в дочернем классе.
Наличие этого метода в суперклассе также гарантирует для клиентского кода, что во всех объектах типа ShopProduct будет присутствовать метод getSummaryLine().
Позже вы увидите, как можно выполнить это требование в базовом классе, не предоставляя никакой его реализации. Каждый дочерний объект класса ShopProduct унаследует все свойства своего родителя. В собственных реализациях метода getSummaryLine() для обоих классов CdProduct и BookProduct обеспечивается доступ к свойству $title.
C понятием наследования сразу разобраться непросто. Определяя класс, который расширяет другой класс, мы гарантируем, что экземпляр его объекта определяется сначала характеристиками дочернего, а затем - родительского класса.
Чтобы понять это, нужно размышлять с точки зрения поиска. При вызове $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() родительского класса."
Давайте изменим наш пример так, чтобы каждый класс оперировал только теми данными, которые имеют к нему отношение.
class ShopProduct { public $title; public $producerMainName; private $producerFirstName; public $price; function __construct( $title, $firstName, $mainName, $price ) { $this->title = $title; $this->producerFirstName = $firstName; $this->producerMainName = $mainName; $this->price = $price; } function getProducer() { return "{$this->producerFirstName}". " {$this->producerMainName}"; } function getSummaryLine() { $base = "$this->title ( $this->producerMainName, "; $base .= "$this->producerFirstName )"; return $base; } } class CdProduct extends ShopProduct { public $playLength; function __construct( $title, $firstName, $mainName, $price, $playLength ) { parent::__construct( $title, $firstName, $mainName, $price ); $this->playLength = $playLength; } function getPlayLength() { return $this->playLength; } function getSummaryLine() { $base = "$this->title ( $this->producerMainName, "; $base .= "$this->producerFirstName )"; $base .= ": Время звучания - $this->playLength"; return $base; } } class BookProduct extends ShopProduct { public $numPages; function __construct( $title, $firstName, $mainName, $price, $numPages ) { parent::__construct( $title, $firstName, $mainName, $price ); $this->numPages = $numPages; } function getNumberOfPages() { return $this->numPages; } function getSummaryLine() { $base = "$this->title ( $this->producerMainName, "; $base .= "$this->producerFirstName )"; $base .= ": страниц - $this->numPages"; return $base; } }
Каждый дочерний класс вызывает конструктор своего родительского класса, прежде чем определять собственные свойства. Базовый класс теперь "знает" только о собственных данных. Дочерние классы - это обычно "специализации" родительских классов.
Следует избегать того, чтобы давать родителским классам какие-либо особые "знания" о дочерних классах.
Вызов переопределенного метода
Ключевое слово parent можно использовать в любом методе, который переопределяет свой эквивалент в родительском классе. Когда мы переопределяем метод, то, возможно, хотим не удалить функции "родителя", а, скорее, расширить их. Достичь этого можно, вызвав метод родительского класса в контексте текущего объекта.
Если вы снова посмотрите на реализацию метода getSummaryLine(), то увидите, что значительная часть кода в них дублируется. И лучше этим воспользоваться, чем заново воспроизводить функциональность, уже разработанную в классе ShopProduct.
// Класс ShopProduct... function getSummaryLine() { $base = "{$this->title} ( {$this->producerMainName}, "; $base = .= "{$this->producerFirstName} )"; return $base; } // Класс BookProduct... function getSummaryLine() { $base = parent::getSummaryLine(); $base .= "страниц - {$this->numPages}"; return $base; }
Мы определили основные функции для метода getSummaryLine() в базовом классе ShopProduct. Вместо того, чтобы воспроизводить их в подклассах CdProduct и BookProduct, мы просто вызовем родительский метод, прежде чем добавлять дополнительные данные к итоговой строке.
Теперь, когда мы познакомились с основами наследования, можно, наконец, рассмотреть вопрос видимости свойств и методов в свете полной картины происходящего.
Об этом читайте в следующем материале "Управление доступом к классам: спецификаторы доступа public, private и protetced".
Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!
Смотрите также: