Как нарушить принцип Барбары Лисков на примере переопределения свойств в подклассе
Что проверяют
Понимаете ли вы, что принцип Лисков про поведение, а не про сигнатуры: типы проверит компилятор, контракт не проверит никто. Просят именно свойства, потому что в Swift переопределение с наблюдателями и геттерами меняет смысл незаметно. Следом спрашивают, как это исправить.
Как отвечать
Сформулируйте принцип: код, работающий с базовым типом, должен работать с любым подтипом без проверок is и без сюрпризов. Подтип не усиливает предусловия, не ослабляет постусловия и не ломает инварианты.
Классика со свойствами: class Rectangle с var width и var height и подкласс Square, который переопределяет свойства так, чтобы при установке одной стороны менялась и вторая. Клиент базового класса пишет rect.width = 5; rect.height = 4, ждёт площадь 20 и получает 16. Постусловие сеттера «меняется только ширина» нарушено.
Что ещё встречается в iOS-коде:
- наблюдатель, который правит значение:
override var value: Int { didSet { if value > 100 { value = 100 } } }. База принимала любое число, подкласс молча обрезает, и после присваиванияvalueуже не равно присвоенному; - геттер-заглушка: переопределённое вычисляемое свойство возвращает константу или вызывает
fatalError(). Это ослабление постусловия в чистом виде; - переопределение
frameилиisHiddenуUIViewс побочными эффектами, которых UIKit при раскладке не ждёт.
Swift частично защищает сам: read-only свойство можно переопределить как read-write, а наоборот компилятор не пропустит. От смены поведения это не спасает.
Исправление: не наследовать там, где контракт не совпадает. Square и Rectangle становятся отдельными типами, часто структурами, с общим протоколом Shape и свойством area. Классы, не задуманные для наследования, помечают final.
Где спотыкаются
Приводят пингвина с fly(). Это пример про методы, а спрашивали про свойства.
Считают, что раз код компилируется, принцип соблюдён. Лисков о поведенческом контракте, типы его не выражают.
Лечат нарушение проверкой if shape is Square в клиенте. Это симптом, а не исправление: каждый новый подкласс потребует правки вызывающего кода.