> Что такое принцип подстановки Лисков из SOLID? (PHP)
Уровень: junior · Язык: PHP · Категория: Технические вопросы
Компании: Travelata
> Пример ответа
Короткий ответ
Принцип подстановки Лисков (Liskov Substitution Principle, LSP) гласит: объекты наследующего класса должны быть взаимозаменяемы с объектами базового класса без нарушения корректности программы. Если код работает с базовым типом, он должен работать и с любым его подтипом, не требуя изменений и не ломая ожидаемое поведение.
Подробное объяснение
LSP - это "буква L" в SOLID, сформулированная Барбарой Лисков в 1987 году. Формально: если S - подтип T, то объекты типа T могут быть заменены объектами типа S без изменения желаемых свойств программы.
На практике это означает, что наследование должно быть не просто "is-a" по названию, а по поведению. Подкласс обязан:
- сохранять контракт базового класса (сигнатуры методов, типы аргументов и возвращаемых значений);
- не усиливать предусловия (не требовать больше, чем базовый класс);
- не ослаблять постусловия (не гарантировать меньше, чем базовый класс);
- не выбрасывать новые исключения, которые не ожидаются базовым классом;
- сохранять инварианты (например, неизменяемость полей).
Классический пример нарушения - класс Rectangle и Square. Квадрат формально является прямоугольником, но если у прямоугольника можно независимо менять ширину и высоту, то квадрат ломает это поведение. Вместо наследования лучше использовать композицию или общий интерфейс с неизменяемыми фигурами.
На практике
В PHP LSP проверяется не только логикой, но и самим языком. PHP строго контролирует сигнатуры методов при наследовании: нельзя изменить тип параметра на более узкий (contravariance нарушена) или тип возврата на более широкий (covariance нарушена). Однако PHP не проверяет бизнес-логику - например, что метод не бросает неожиданное исключение.
Типичные практические ситуации:
- если подкласс переопределяет метод и добавляет проверку, которой не было в базовом классе - это усиление предусловия, нарушение LSP;
- если подкласс возвращает
nullтам, где базовый класс возвращал объект - ослабление постусловия; - если подкласс бросает
ExceptionвместоRuntimeException, которое ожидал вызывающий код - тоже нарушение.
Хороший способ избежать проблем - использовать интерфейсы с чётким контрактом и предпочитать композицию наследованию. Если наследование неизбежно, пишите тесты, которые проверяют поведение через базовый тип.
Пример кода
PHP<?php// Нарушение LSPclass Rectangle {public function __construct(protected int $width,protected int $height) {}public function setWidth(int $width): void {$this->width = $width;}public function setHeight(int $height): void {$this->height = $height;}public function area(): int {return $this->width * $this->height;}}class Square extends Rectangle {public function setWidth(int $width): void {$this->width = $width;$this->height = $width;}public function setHeight(int $height): void {$this->height = $height;$this->width = $height;}}function printArea(Rectangle $rect): void {$rect->setWidth(5);$rect->setHeight(10);echo $rect->area(); // Для Square выведет 100, а ожидается 50}// Корректное решение - неизменяемые фигурыinterface Shape {public function area(): int;}final class Rectangle implements Shape {public function __construct(private int $width,private int $height) {}public function area(): int {return $this->width * $this->height;}}final class Square implements Shape {public function __construct(private int $side) {}public function area(): int {return $this->side * $this->side;}}
Как отвечать на собеседовании
Начните с формального определения, затем переведите на простой язык: "подкласс не должен ломать ожидания, которые есть у кода, работающего с базовым классом". Приведите пример нарушения - квадрат и прямоугольник - и объясните, почему это плохо. Затем покажите, как исправить через интерфейс или композицию. Если спросят про PHP, упомяните, что язык контролирует сигнатуры, но не бизнес-логику, поэтому ответственность на разработчике. Завершите практическим советом: тестируйте поведение через базовый тип.
Что проверяет интервьюер
Интервьюер оценивает:
- понимание разницы между синтаксическим наследованием и поведенческим контрактом;
- способность привести конкретный пример нарушения и объяснить последствия;
- знание способов решения - интерфейсы, композиция, неизменяемые объекты;
- понимание ограничений PHP в контексте LSP;
- умение связать LSP с другими принципами SOLID, например, с открытостью/закрытостью.
Типичные ошибки
- Сводить LSP только к "наследник должен быть подтипом" без объяснения поведенческого контракта.
- Приводить пример с квадратом и прямоугольником, но не объяснять, почему именно нарушение - важно показать, что ломается ожидание независимого изменения сторон.
- Утверждать, что PHP полностью защищает от нарушения LSP - это не так, проверяется только сигнатура, а не логика.
- Предлагать исправить нарушение добавлением
ifв метод - это маскировка проблемы, а не решение. - Путать LSP с принципом единственной ответственности или инверсией зависимостей.
> Похожие задачи по PHP
Расскажите про типизацию в PHP
Что происходит в контроллере после получения запроса
С какими эволюционными базами данных ты работал
Какие тесты ты пишешь: функциональные или юнит тесты?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью