Абстрактные классы, final и static
Иногда класс существует только как основа для других: «фигура вообще» не имеет площади, а круг и квадрат имеют. Такой класс делают абстрактным. А иногда, наоборот, нужно запретить менять поведение в наследниках — для этого есть final.
Абстрактный класс
Абстрактный класс нельзя создать через new. Он описывает общее поведение и объявляет методы, которые наследники обязаны написать:
<?php
abstract class Shape
{
abstract public function area(): float;
public function describe(): string
{
return 'Площадь: ' . round($this->area(), 2);
}
}
class Circle extends Shape
{
public function __construct(private float $radius) {}
public function area(): float
{
return M_PI * $this->radius ** 2;
}
}
echo (new Circle(1))->describe(); // Площадь: 3.14У абстрактного метода нет тела — только заголовок. Метод describe() обычный: он достаётся всем наследникам готовым.
Создать абстрактный класс нельзя
<?php
abstract class Shape {}
try {
$shape = new Shape();
} catch (Error $e) {
echo $e->getMessage(); // Cannot instantiate abstract class Shape
}Абстрактный класс или интерфейс
| Абстрактный класс | Интерфейс | |
|---|---|---|
| Готовые методы | Да | Нет |
| Свойства | Да | Нет |
| Сколько можно указать | Один родитель | Сколько угодно |
| Когда выбирать | У наследников есть общий код | Нужен только договор о методах |
final
Слово final перед методом запрещает переопределять его, перед классом — наследовать класс:
<?php
class Order
{
final public function number(): string
{
return 'ORD-1';
}
}
final class Money
{
public function __construct(public readonly int $amount) {}
}
echo (new Order())->number(); // ORD-1Попытка написать class Bonus extends Money закончится ошибкой ещё до запуска программы.
self и static
Внутри класса self означает класс, где написан код, а static — класс, у которого метод вызван на самом деле:
<?php
class Model
{
public static function create(): static
{
return new static();
}
public static function createSelf(): self
{
return new self();
}
}
class User extends Model {}
echo get_class(User::create()), "\n"; // User
echo get_class(User::createSelf()); // ModelЭто называют поздним статическим связыванием. В методах, создающих объекты, почти всегда нужен static.
Шаблонный метод
Абстрактный класс и final вместе дают удобный приём: родитель задаёт порядок действий, а наследники — их содержание:
<?php
abstract class Report
{
final public function render(): string
{
return $this->title() . "\n" . implode("\n", $this->rows());
}
abstract protected function title(): string;
abstract protected function rows(): array;
}
class SalesReport extends Report
{
protected function title(): string
{
return 'Продажи';
}
protected function rows(): array
{
return ['Чай: 3', 'Кофе: 5'];
}
}
echo (new SalesReport())->render();Наследник не может изменить render(), но определяет заголовок и строки.
Частая ошибка
Длинные цепочки наследования — Model → User → Admin → SuperAdmin — трудно понимать и менять: правка в родителе неожиданно ломает наследников. Если классам нужен только общий набор методов, берите интерфейс; если общий код — трейт или отдельный объект, переданный в конструктор. Абстрактный класс оставляйте для случаев, когда наследники действительно являются разновидностями одного и того же.