День четыреста шестидесятый. #DesignPatterns
Принципы SOLID.
3. Принцип подстановки Лисков (LSP)
«Должна существовать возможность использовать объекты производного класса вместо объектов базового класса. Это значит, что объекты производного класса должны вести себя согласованно, согласно контракту базового класса». (Уорд Каннингем. Liskov Substitution Principle).
Наследование обычно моделирует отношение «ЯВЛЯЕТСЯ» (IS-A Relationship) между классами. Говорят, что экземпляр наследника также ЯВЛЯЕТСЯ экземпляром базового класса, что выражается в возможности использования экземпляров наследника везде, где ожидается использование базового класса. Данный вид наследования называется также наследованием подтипов. Принцип подстановки Лисков призван помочь в корректной реализации этого вида наследования, или отказаться от наследования, если его корректная реализация невозможна.
Для чего нужен принцип подстановки Лисков
Основной смысл любой иерархии наследования в том, что она позволяет использовать базовые классы полиморфным образом, не задумываясь о том, экземпляр какого конкретного класса был передан. С одной стороны, любая реализация должна следовать некоторому абстрактному протоколу или контракту, а с другой — она должна иметь возможность выбрать конкретный способ реализации этого протокола. Контракт описывает ожидаемое видимое поведение абстракции, оставляя реализации решать, каким образом это поведение будет реализовано. Если же реализация (то есть наследники) не будет знать об этом протоколе или не будет ему следовать, то в приложении мы будем вынуждены обрабатывать конкретную реализацию специальным образом, что сводит на нет преимущества использования наследования и полиморфизма.
Квадраты и прямоугольники
Является ли квадрат прямоугольником? Это зависит от того, какое поведение мы будем приписывать фигурам, т.е. от спецификации или контракта (ожидаемого поведения) этих сущностей. С точки зрения математики квадрат является прямоугольником, но актуально ли это отношение для классов? Попытаемся сформулировать контракты этих классов:
- Прямоугольник: ширина и высота положительны.
- Квадрат: ширина и высота положительны и равны.
Поскольку у квадрата все стороны равны, то изменение его ширины должно приводить к изменению высоты и наоборот. Это значит, контракт свойств ширины и высоты квадрата становится несогласованным с контрактом этих свойств прямоугольника. С точки зрения клиента прямоугольника свойства полностью независимы, а значит, замена прямоугольника квадратом во время исполнения нарушит это предположение клиента.
Но это не значит, что данная иерархия наследования является невозможной. Квадрат перестаёт быть нормальным прямоугольником, только если квадрат и прямоугольник являются изменяемыми. Если сделать их неизменяемыми, проблема с нарушением поведения клиентского кода при замене прямоугольников квадратами пропадет.
Данный пример показывает несколько важных моментов:
1. Что наличие контракта позволяет чётко понять, нарушает ли производный класс принцип LSP.
2. Пользу неизменяемости в ООП: контракт неизменяемых типов проще, поскольку контролируется лишь конструктором и инвариантом класса.
3. Почему некоторые специалисты рекомендуют, чтобы классы были либо абстрактными, либо запечатанными (sealed) и не было возможности создавать экземпляры классов из середины иерархии наследования. Например, в этом случае можно выделить промежуточный абстрактный класс «четырехугольник», от которого уже наследуются квадрат и прямоугольник.
>>>
Post #557
1.09K