LSP — Liskov Substitution Principle
Принцип подстановки Барбары Лисков можно сформулировать так:
- Производные классы должны быть заменяемы их базовыми классами.
В названии принципа фигурирует фамилия Лисков. Это связано с тем, что принцип впервые сформулировала (используя другую формулировку) Барбара Лисков.
Кажется логичным, что наследующие классы, или подклассы, должны заменять свои базовые, или родительские, классы. Но, конечно, принцип не просто утверждает очевидное. Анализируя его, мы признаём две концептуальные части: сначала речь идёт о производных и базовых классах, потом — о замене.
Производный класс — класс, который расширяет какой-либо другой, базовый класс. Базовый класс может быть конкретным или абстрактным классом, а также интерфейсом.
- Если базовый класс является конкретным, у него нет отсутствующих (также известных как виртуальных) методов. В этом случае производный класс, или подкласс, переопределяет один или несколько методов, которые уже реализованы в родительском классе.
- Если базовый класс является абстрактным, существует один или несколько чистых виртуальных методов, которые должны быть реализованы с помощью производного класса.
- Если все методы базового класса — чисто виртуальные методы (то есть у них есть только сигнатура и нет тела), то обычно базовый класс называется интерфейсом.
Чтобы убедиться, что мы не запутались, взглянем на простой код:
Теперь вы знаете всё о базовых и производных классах. Но что значит «производные классы могут быть заменяемыми»?
В целом быть заменяемым — значит вести себя хорошо как подкласс или класс, реализуя интерфейс. Вести себя хорошо — значит вести себя как ожидалось или как было согласовано. Объединяя обе концепции, принцип подстановки Барбары Лисков гласит, что, если мы создаём класс, который расширяет другой класс или реализует интерфейс, он должен вести себя так, как и ожидалось.
Слова «вести себя так, как и ожидалось» по-прежнему довольно расплывчаты. Вот почему указывать на нарушения принципа подстановки Лисков может быть довольно сложно. Среди разработчиков даже порой возникают разногласия по поводу того, что считать нарушением этого принципа. Иногда это дело вкуса, а иногда — зависит от самого языка программирования и конструкций объектно ориентированного программирования, которые он предлагает.
Рассмотрим пример нарушения этого принципа.
Класс DevOps
нарушил логику своего родителя Developer
, тем самым нарушив принцип LSP. Потому что, в соответствии с принципом, клиент, который использует класс Developer
, должен иметь возможность заменить его на любой дочерний класс и не сломать программу. В случае с дочерним классом DevOps
программа станет выдавать ошибку.
Следующий пример демонстрирует возможность клиента использовать класс и его потомков без нарушения логики программы.
Как видите, функция move может без ошибок работать как с классом Character
, так и с его потомками.
LSP — основа хорошего объектно ориентированного проектирования программного обеспечения, потому что коррелирует с одним из базовых принципов ООП — полиморфизмом. Речь о том, чтобы создавать правильные иерархии, в которых производные классы являются полиморфными для их родителя по отношению к методам его интерфейсов.
Интересно отметить, как этот принцип связан с предыдущим — принципом открытости/закрытости. Если мы попытаемся расширить класс с помощью нового, несовместимого с ним класса, всё сломается. Взаимодействие с клиентом будет нарушено, и в результате такое расширение станет невозможным (чтобы сделать это возможным, пришлось бы нарушить другой принцип и модифицировать код клиента, который должен быть закрыт для модификации, — такое крайне нежелательно и неприемлемо).
Создание новых классов в соответствии с LSP помогает расширять иерархию классов правильно.
📂 SOLID | Последнее изменение: 26.04.2024 10:02