Абстрактные классы и интерфейсы
В прошлом уроке дочерний класс получал от родителя готовые методы. Но бывает, что родитель сам не знает, как сделать часть работы. Возьмём фигуры: у квадрата и у круга есть площадь, но считают её по-разному, а построить «фигуру вообще» нельзя.
В семействе чертежей это чертёж-заготовка: стены и крыша нарисованы, а на месте кухни написано «дорисуйте сами». По заготовке дом не строят, её сначала дополняют.
Есть и другой документ — должностная инструкция. В ней перечислено, что работник обязан уметь, но не сказано, как именно это делать.
В PHP заготовка — это абстрактный класс, а инструкция — интерфейс.
Абстрактный класс
Абстрактный класс — класс с пометкой abstract, объект которого создать нельзя: он существует только как родитель для других классов.
<?php
abstract class Shape
{
public function __construct(protected float $size) {}
abstract public function area(): float;
public function describe(): string
{
return $this::class . ' ' . $this->area();
}
}Разберём по шагам:
abstract class Shapeобъявляет заготовку: построить объектShapeнельзя.- Конструктор и свойство
$sizeтакие же, как у обычного класса: данные абстрактный класс хранить может. abstract public function area(): float;— абстрактный метод. У него есть только сигнатура, то есть имя, параметры и тип результата, а вместо тела стоит точка с запятой. Каждый дочерний класс обязан его написать.describe()— готовый метод. Он вызывает$this->area(), хотя вShapeу этого метода нет тела: как вы уже знаете,$thisнайдёт версию из класса объекта. А$this::classдаёт строкой имя класса объекта.
Здесь картинка с заготовкой неточна: саму заготовку в PHP не дорисовывают, а пишут дочерний класс с недостающими методами.
Интерфейс
Интерфейс — список методов, которые класс обещает иметь, без их кода. Его объявляют словом interface, а класс подписывается на него словом implements:
interface HasCorners
{
public function corners(): int;
}
class Square extends Shape implements HasCorners
{
public function area(): float
{
return $this->size ** 2;
}
public function corners(): int
{
return 4;
}
}interface HasCornersобъявляет один методcorners()без тела. Методы интерфейса всегдаpublic.SquareнаследуетShapeи реализуетHasCorners, поэтому пишет оба метода:area()для родителя иcorners()для интерфейса.- Родитель у класса один, а интерфейсов может быть сколько угодно:
implements A, B.
Кроме методов, интерфейс может объявлять константы. Свойств со значениями и готового кода в нём не бывает. С PHP 8.4 интерфейс может требовать и свойство, но значения у такого свойства в интерфейсе всё равно нет.
Если в классе нет одного из методов интерфейса, PHP остановит скрипт с фатальной ошибкой на объявлении класса:
Fatal error: Class Square contains 1 abstract method and must therefore be declared abstract or implement the remaining method (HasCorners::corners)Абстрактный класс или интерфейс
Разницу между абстрактным классом и интерфейсом часто спрашивают на собеседовании по PHP. Ответ удобно держать в голове таблицей:
| Абстрактный класс | Интерфейс | |
|---|---|---|
| Свойства со значениями | можно | нельзя |
| Методы с телом | можно | нельзя |
| Сколько у одного класса | один, через extends | сколько угодно, через implements |
Объект через new | нельзя | нельзя |
Абстрактный класс берут, когда у семейства классов общие данные и общий код. Интерфейс — когда разным классам нужно одно и то же умение, а код у каждого свой.
instanceof и полиморфизм
Добавим круг и посмотрим на фигуры вместе:
class Circle extends Shape
{
public function area(): float
{
return 3.14 * $this->size ** 2;
}
}
$sq = new Square(3);
foreach ([$sq, new Circle(1)] as $shape) {
echo $shape->describe(), '; '; // Square 9; Circle 3.14;
}
echo "\n";
var_dump($sq instanceof Shape); // bool(true)
var_dump(new Circle(1) instanceof HasCorners); // bool(false)Давайте разберём, что напечаталось:
- Цикл вызывает у каждой фигуры один и тот же
describe(), а площадь каждая считает по-своему. Это полиморфизм из прошлого урока: код цикла не знает, квадрат перед ним или круг. area()у квадрата возвращает9.0типаfloat, аechoпечатает такое число без.0— как9.instanceofпроверяет, относится ли объект к классу или интерфейсу.$sq instanceof Shapeдаётtrue, хотя$sq— объектSquare: проверка проходит по всей цепочке родителей и по всем интерфейсам.Circleне реализуетHasCorners, поэтому второй ответ —false. Если слева отinstanceofстоит не объект, ответ тожеfalse, а не ошибка.
Здесь аналогия с инструкцией неточна. Человек может уметь всё, что в ней написано, и без подписи. А в PHP instanceof HasCorners даёт true только для класса, который написал implements HasCorners: одного метода corners() мало.
Частая ошибка
Пытаются создать объект абстрактного класса:
$shape = new Shape(2); // ✗Fatal error: Uncaught Error: Cannot instantiate abstract class ShapeАбстрактный класс — заготовка, и PHP не даёт построить по ней объект. С интерфейсом то же самое: new HasCorners() даёт Cannot instantiate interface HasCorners. Исправление — создать объект дочернего класса, например new Square(2).
Главное
- Абстрактный класс — заготовка: объект из него не создать, но в нём бывают свойства, конструктор, готовые и абстрактные методы.
- Интерфейс перечисляет методы без тел; класс подписывается через
implementsи обязан написать каждый метод. - Родитель у класса один, а интерфейсов может быть сколько угодно.
instanceofистинен для класса объекта, всех его родителей и всех реализованных интерфейсов.