Reg.ru: домены и хостинг

Крупнейший регистратор и хостинг-провайдер в России.

Более 2 миллионов доменных имен на обслуживании.

Продвижение, почта для домена, решения для бизнеса.

Более 700 тыс. клиентов по всему миру уже сделали свой выбор.

Перейти на сайт->

Бесплатный Курс "Практика HTML5 и CSS3"

Освойте бесплатно пошаговый видеокурс

по основам адаптивной верстки

на HTML5 и CSS3 с полного нуля.

Начать->

Фреймворк Bootstrap: быстрая адаптивная вёрстка

Пошаговый видеокурс по основам адаптивной верстки в фреймворке Bootstrap.

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

Верстайте на заказ и получайте деньги.

Получить в подарок->

Бесплатный курс "Сайт на WordPress"

Хотите освоить CMS WordPress?

Получите уроки по дизайну и верстке сайта на WordPress.

Научитесь работать с темами и нарезать макет.

Бесплатный видеокурс по рисованию дизайна сайта, его верстке и установке на CMS WordPress!

Получить в подарок->

*Наведите курсор мыши для приостановки прокрутки.


Работа с наследованием в 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".

Понравился материал и хотите отблагодарить?
Просто поделитесь с друзьями и коллегами!


Смотрите также:

PHP: Получение информации об объекте или классе, методах, свойствах и наследовании

PHP: Получение информации об объекте или классе, методах, свойствах и наследовании

CodeIgniter: жив или мертв?

CodeIgniter: жив или мертв?

Функции обратного вызова, анонимные функции и механизм замыканий

Функции обратного вызова, анонимные функции и механизм замыканий

Применение функции к каждому элементу массива

Применение функции к каждому элементу массива

Слияние массивов. Преобразование массива в строку

Слияние массивов. Преобразование массива в строку

Деструктор и копирование объектов с помощью метода __clone()

Деструктор и копирование объектов с помощью метода __clone()

Эволюция веб-разработчика или Почему фреймворк - это хорошо?

Эволюция веб-разработчика или Почему фреймворк - это хорошо?

Магические методы в PHP или методы-перехватчики (сеттеры, геттеры и др.)

Магические методы в PHP или методы-перехватчики (сеттеры, геттеры и др.)

PHP: Удаление элементов массива

PHP: Удаление элементов массива

Ключевое слово final (завершенные классы и методы в PHP)

Ключевое слово final (завершенные классы и методы в PHP)

50 классных сервисов, программ и сайтов для веб-разработчиков

50 классных сервисов, программ и сайтов для веб-разработчиков

Наверх