Ответ
Принцип подстановки Лисков (LSP) — третий принцип SOLID. Он гласит: объекты в программе должны быть заменяемы экземплярами их подтипов (наследников) без изменения корректности этой программы.
Проще говоря: Если у вас есть функция, работающая с классом Родитель, то она должна безошибочно работать и с любым классом Ребёнок, унаследованным от Родителя. Наследник не должен нарушать контракт базового класса.
Ключевые аспекты контракта:
- Предусловия (требования к входным данным) не могут быть усилены в подклассе.
- Постусловия (гарантии на выходе) не могут быть ослаблены в подклассе.
- Инварианты (условия, истинные на протяжении всей жизни объекта) должны сохраняться.
Классический пример нарушения LSP:
// Базовый класс, предполагающий, что все птицы летают.
class Bird {
public function fly(): int {
return $this->airSpeedVelocity; // Возвращает скорость полёта
}
}
// Подкласс, нарушающий контракт. Пингвин не летает.
class Penguin extends Bird {
public function fly(): int {
throw new RuntimeException('Penguins cannot fly!'); // Усилено предусловие (теперь нужно НЕ летать)
// или return 0; // Ослаблено постусловие (скорость полёта не имеет смысла)
}
}
// Функция, рассчитанная на работу с Bird.
function traverseForest(array $birds) {
foreach ($birds as $bird) {
$speed = $bird->fly(); // Для Penguin выбросится исключение, программа сломается.
// ...
}
}
Как исправить, следуя LSP: Нужно пересмотреть иерархию наследования, чтобы она отражала реальные возможности объектов.
// Более точная абстракция.
abstract class Bird {
// Общие для всех птиц методы (есть, пить, размножаться)
}
// Интерфейс для способности, а не для сущности.
interface Flyable {
public function fly(): int;
}
class Sparrow extends Bird implements Flyable {
public function fly(): int {
return 15; // OK
}
}
class Penguin extends Bird {
public function swim(): int {
return 10; // OK, пингвин плавает
}
}
// Теперь функция работает только с летающими объектами.
function traverseForest(array $flyableBirds) {
foreach ($flyableBirds as $bird) {
$speed = $bird->fly(); // Гарантированно безопасно
}
}
Соблюдение LSP делает систему устойчивой к изменениям и позволяет безопасно использовать полиморфизм.
Ответ 18+ 🔞
Давай разжую тебе этот принцип, а то звучит как заклинание из учебника по высшей магии. Принцип подстановки Лисков, ёпта. Суть в чём: если ты написал код для какого-то класса, то он должен работать и для всех его детей, внуков и прочих наследничков. Без сюрпризов, без пиздеца и внезапных исключений.
Говоря человеческим языком: Подставляй наследника вместо родителя — и ничего не должно ебнуться. Если функция ждёт Папу, она должна спокойно принять и Сына, не заметив подмены. А если заметила — значит, архитектура говно, и наследник нарушил негласный договор с предком.
В чём суть этого договора, спросишь ты? А вот в чём:
- Предусловия (что нужно для работы метода) — наследник не может быть привередливее родителя. Нельзя требовать больше, чем просил папаша.
- Постусловия (что метод гарантирует на выходе) — наследник не может схалтурить и обещать меньше, чем обещал родитель.
- Инварианты (что всегда истинно для объекта) — эти священные истины должны оставаться незыблемыми во всей династии.
Классический пиздец, где всё пошло по пизде:
// Допустим, есть класс "Птица". Логично же, птицы летают?
class Bird {
public function fly(): int {
return $this->airSpeedVelocity; // Возвращает скорость, всё чётко.
}
}
// А вот и наш ушастый пингвин, мартышлюшка нелетающая.
class Penguin extends Bird {
public function fly(): int {
throw new RuntimeException('А пингвины не летают, мудила!'); // Нахуй сломали контракт!
// Или вот так: return 0; // Типа летит со скоростью ноль. Это тоже пиздец, ослабили гарантии.
}
}
// А тут у нас функция, которая гоняет стаю птиц по лесу.
function traverseForest(array $birds) {
foreach ($birds as $bird) {
$speed = $bird->fly(); // И тут БАБАХ! Для пингвина — исключение. Всё, пизда программе.
}
}
Видишь, в чём ядрёна вошь? Мы подсунули наследника (Penguin) вместо родителя (Bird), а программа накрылась медным тазом. Это и есть нарушение LSP в чистом виде.
Как сделать по-человечески, без этих пиздопроебибен?
Надо перестать выдумывать иерархии из головы и отражать реальность. Не все птицы летают — значит, и класс Bird не должен про это врать.
// Вот более честная абстракция. Просто птица, без обещаний.
abstract class Bird {
// Общие для всех птиц штуки: есть, пить, срать.
}
// А способность летать — это отдельный скилл, интерфейс.
interface Flyable {
public function fly(): int;
}
// Воробей — птица И летающая. Всё ок.
class Sparrow extends Bird implements Flyable {
public function fly(): int {
return 15; // Летит себе, красавец.
}
}
// Пингвин — птица, но не летающая. И не надо ему эту хуйню впихивать.
class Penguin extends Bird {
public function swim(): int {
return 10; // Зато плавает, сука, как торпеда!
}
}
// И теперь наша функция работает ТОЛЬКО с теми, кто реально умеет летать.
// Никаких сюрпризов, доверия — овердохуища.
function traverseForest(array $flyableBirds) {
foreach ($flyableBirds as $bird) {
$speed = $bird->fly(); // Тут можно спать спокойно, исключений не будет.
}
}
Вот и весь принцип, бля. Соблюдаешь его — система становится предсказуемой, как утро после тяжелого дня: ты точно знаешь, что тебя ждёт. Нарушаешь — получаешь волнение ебать, когда вроде бы свой же код выстреливает тебе в ногу из-за какого-нибудь хитрожопого наследника-полупидора.