Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Абстрактные классы, 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 — трудно понимать и менять: правка в родителе неожиданно ломает наследников. Если классам нужен только общий набор методов, берите интерфейс; если общий код — трейт или отдельный объект, переданный в конструктор. Абстрактный класс оставляйте для случаев, когда наследники действительно являются разновидностями одного и того же.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.