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

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

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

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

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

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

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

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

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

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

Начать->

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

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

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

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

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

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

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

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

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

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

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

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


Абстрактные классы и интерфейсы в PHP

Перед изучением данной статьи вы можете прочитать предыдущую статью из этой серии - "Статические методы и свойства в PHP".


Абстрактные классы в PHP

Появление абстрактных классов в свое время стало одним из главных нововведений в PHP 5. Это было еще одним подтверждением растущей приверженности PHP объектно-ориентированному проектированию.

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

Абстрактный класс определяется с помощью ключевого слова abstract. Давайте переопределим класс ShopProductWriter, который мы создали в одном из предыдущих материалов, в виде абстрактного класса.


abstract class ShopProductWriter {
    protected $products = array();

    public function addProduct( ShopProduct $shopProduct ) {
        $this->products[]=$shopProduct;
    }
}

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


$writer = new ShopProductWriter();
// Выводится:
// Fatal error: Cannot instantiate abstract class ShopProductWriter ...

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

Абстрактный метод не может иметь реализацию в абстрактном классе. Он объявляется как обычный метод, но объявление заканчивается точкой с запятой, а не телом метода. Давайте добавим абстрактный метод write() к классу ShopProductWriter.


abstract class ShopProductWriter {
    protected $products = array();

    public function addProduct( ShopProduct $shopProduct ) {
        $this->products[]=$shopProduct;
    }

    abstract public function write( );
}

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

Если бы мы, как показано ниже, создали класс, производный от ShopProductWriter, в котором метод write() не был бы реализован


class ErrorWriter extends ShopProductWriter{};

то столкнулись бы со следующей ошибкой.


PHP Fatal error: Class ErrorWriter contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (ShopProductWriter::write) in ...

В переводе на русский это звучит так:

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

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

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

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

Ниже приведены две реализации класса ShopProductWriter.


class XmlProductWriter extends ShopProductWriter{

    public function write() {
        $str = '<?xml version="1.0" encoding="UTF-8"?>'."\n";
        $str .= "<products>\n";
         foreach ( $this->products as $shopProduct ) {
            $str .= "\t<product title=\"{$shopProduct->getTitle()}\">\n";
            $str .= "\t\t<summary>\n";
            $str .= "\t\t{$shopProduct->getSummaryLine()}\n";
            $str .= "\t\t</summary>\n";
            $str .= "\t</product>\n";
        }
        $str .= "</products>\n";
        print $str;
    }
}

class TextProductWriter extends ShopProductWriter{

    public function write() {
        $str = "Товары:\n";
        foreach ( $this->products as $shopProduct ) {
            $str .= $shopProduct->getSummaryLine()."\n";
        }
        print $str;
    }
}

Мы создали два класса, каждый с собственной реализацией метода write(). Первый выводит данные о товаре в формате XML, а второй - в текстовом виде.

Теперь методу, которому требуется передать объект типа ShopProductWriter, не нужно точно знать, какой из этих двух классов он получает, поскольку ему достоверно известно, что в обоих классах реализован метод write().

Обратите внимание на то, что мы не проверяем тип переменной $products, прежде чем использовать ее как массив. Причина в том, что это свойство инициализируется как пустой массив в классе ShopProductWriter.


Интерфейсы в PHP

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

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

Для объявления интерфейса используется ключевое слово interface. Интерфейс может содержать объявления свойств и методов, но не тела этих методов.

Давайте определим интерфейс.


interface Chargeable {
    public function getPrice();
}

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

При реализации интерфейса в классе, имя интерфейса указывается в объявлении этого класса после ключевого слова implements.

После этого процесс реализации интерфейса станет точно таким же, как расширение абстрактного класса, который содержит только абстрактные методы. Давайте сделаем так, чтобы в классе ShopProduct был реализован интерфейс Chargeable.


class ShopProduct implements Chargeable {
    // ...
    public function getPrice() {
        return ( $this->price - $this->discount );
    }
    // ...
}

В классе ShopProduct уже есть метод getPrice(), что же может быть полезного в реализации интерфейса Chargeable?

И снова ответ связан с типами. Дело в том, что реализующий класс принимает тип класса и интерфейса, который он расширяет. Это означает, что класс CDProduct относится к следующим типам:

- CDProduct
- ShopProduct
- Chargeable

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


public function CDInfo( CDProduct $prod ) {
	// ...
}

"знает", что у объекта $prod есть метод getPlayLength() в дополнение ко всем методам, определенным в классе ShopProduct и интерфейсе Chargeable.

Если тот же самый объект CDProduct передается методу


publiс function addProduct( ShopProduct $prod ) {
	// ...
}

то известно, что объект $prod поддерживает все методы, определенные в классе ShopProduct. Однако без дальнейшей проверки данный метод ничего не будет знать о методе getPlayLength().

И снова, если передать тот же объект CDProduct методу


public function addChargeableItem ( Chargeable $item ) {
	// ...
}

данному методу ничего не будет известно обо всех методах, определенных в классах ShopProduct или CDProduct. При этом интерпретатор только проверит, содержит ли аргумент $item метод getPrice().

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


class Shipping implements Chargeable {

	public function getPrice() {
		// ...
	}
}

Затем объект типа Shipping мы можем передать методу addChargeableItem(), точно так же, как мы передавали ему объект типа shopProduct.

Для клиента, работающего с объектом типа Chargeable, очень важно то, что он может вызвать метод getPrice(). Любые другие имеющиеся методы связаны с другими типами - через собственный класс объекта, суперкласс или другой интерфейс. Но они не имеют никакого отношения к нашему клиенту.

В классе можно как расширить суперкласс, так и реализовать любое количество интерфейсов. При этом ключевое слово extends должно предшествовать ключевому слову implements.


class Concultancy extends TimedService implements Bookable, Chargeable {
	// ...
}

Обратите внимание на то, что в классе Concultancy реализуется более одного интерфейса. После ключевого слова implements можно перечислить через запятую несколько интерфейсов.

При этом важно помнить, что в PHP поддерживается наследование только от одного родителя (так называемое одиночное наследование), поэтому после ключевого слова extends можно указать только одно имя базового класса.

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Наверх