> Что такое принцип подстановки Лисков из 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
// Нарушение LSP
class 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 с принципом единственной ответственности или инверсией зависимостей.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью