В объектно-ориентированном программировании бывают ситуации, когда класс описывает общую концепцию, но сам по себе не имеет смысла как самостоятельная сущность. Отчёт бывает PDF или Excel. Платёж бывает картой или через СБП. Именно для таких случаев в PHP существуют абстрактные классы — шаблоны, которые нельзя создать напрямую, но можно и нужно наследовать.
Что такое абстрактный класс
Абстрактный класс объявляется с ключевым словом abstract перед class. Он может содержать как обычные методы с готовой реализацией, так и абстрактные методы — методы без тела, которые обязан реализовать каждый наследник. Создать объект абстрактного класса напрямую через new невозможно.
abstract class ReportGenerator
{
public function __construct(
protected string $title,
protected array $data
) {}
// Абстрактный метод — реализацию задаёт наследник
abstract public function render(): string;
// Обычный метод — общий для всех наследников
public function generate(): void
{
echo "=== {$this->title} ===\n";
echo $this->render();
echo "\nСтрок данных: " . count($this->data) . "\n";
}
}
// Конкретный класс обязан реализовать render()
class PdfReport extends ReportGenerator
{
public function render(): string
{
return "[PDF] Формируем документ с " . count($this->data) . " записями.\n";
}
}
class ExcelReport extends ReportGenerator
{
public function render(): string
{
return "[Excel] Заполняем таблицу с " . count($this->data) . " строками.\n";
}
}
$data = ['Январь' => 150, 'Февраль' => 230, 'Март' => 180];
$pdf = new PdfReport('Продажи Q1', $data);
$excel = new ExcelReport('Продажи Q1', $data);
$pdf->generate();
// === Продажи Q1 ===
// [PDF] Формируем документ с 3 записями.
// Строк данных: 3
$excel->generate();
// === Продажи Q1 ===
// [Excel] Заполняем таблицу с 3 строками.
// Строк данных: 3
Метод generate() написан один раз в абстрактном классе и работает одинаково для всех наследников. А render() каждый реализует по-своему. Именно в этом сила: общий алгоритм — в родителе, уникальные детали — в потомках.
Попытка создать объект абстрактного класса напрямую вызовет фатальную ошибку:
new ReportGenerator(...) → Cannot instantiate abstract class ReportGenerator
Правила абстрактных методов
Абстрактный метод — это объявление без реализации. Он задаёт контракт: указывает имя, параметры и тип возвращаемого значения. Несколько важных правил:
abstract class Payment
{
// Абстрактный метод может быть только public или protected
abstract public function charge(float $amount): bool;
abstract protected function buildPayload(): array;
// Обычные методы — с реализацией — тоже допустимы
public function process(float $amount): void
{
$payload = $this->buildPayload();
$success = $this->charge($amount);
echo $success ? "Платёж прошёл\n" : "Ошибка платежа\n";
}
}
class CardPayment extends Payment
{
public function __construct(private string $cardNumber) {}
public function charge(float $amount): bool
{
echo "Списываем {$amount} ₽ с карты {$this->cardNumber}\n";
return true;
}
protected function buildPayload(): array
{
return ['card' => $this->cardNumber];
}
}
$pay = new CardPayment('**** 4242');
$pay->process(1500.00);
// Списываем 1500 ₽ с карты **** 4242
// Платёж прошёл
Абстрактный класс vs интерфейс
На первый взгляд абстрактный класс и интерфейс похожи: оба задают контракт, оба нельзя создать напрямую. Но у них разные задачи.
На практике их часто комбинируют: интерфейс задаёт контракт, абстрактный класс предоставляет частичную реализацию, конкретный класс завершает картину.
interface Reportable
{
public function render(): string;
}
// Абстрактный класс реализует интерфейс частично
abstract class BaseReport implements Reportable
{
public function __construct(protected string $title) {}
public function getTitle(): string
{
return $this->title;
}
// render() остаётся абстрактным — реализует конкретный наследник
}
class HtmlReport extends BaseReport
{
public function render(): string
{
return "<h1>{$this->title}</h1>";
}
}
$report = new HtmlReport('Итоги квартала');
echo $report->render(); // <h1>Итоги квартала</h1>
var_dump($report instanceof Reportable); // true
var_dump($report instanceof BaseReport); // true
Абстрактный класс — это архитектурное решение. Он говорит: «Вот общая логика и структура, но часть деталей каждый наследник определяет сам». Это избавляет от дублирования кода и одновременно обязывает потомков реализовать нужное поведение.
Используйте абстрактный класс, когда у группы классов есть общее состояние и поведение, но один или несколько методов зависят от конкретного типа. Когда же нужен только контракт без какой-либо реализации — выбирайте интерфейс.
0 Комментарий(я)
Зарегистрируйтесь чтобы оставить комментарий