Ответ
Принцип подстановки Лисков (LSP) гласит: объекты в программе должны быть заменяемыми экземплярами их базовых типов без изменения корректности этой программы. Наследующий класс должен:
- Выполнять все контракты (обещания) родительского класса.
- Не накладывать более строгих предусловий (требований к входным данным).
- Не ослаблять постусловий (гарантий на выходные данные или состояние).
Пример нарушения LSP
class Rectangle {
var width: Double = 0
var height: Double = 0
var area: Double {
return width * height
}
func setDimensions(width: Double, height: Double) {
self.width = width
self.height = height
}
}
class Square: Rectangle {
// Нарушение LSP: Квадрат изменяет ожидаемое поведение родителя.
// Для Rectangle width и height независимы, для Square — связаны.
override func setDimensions(width: Double, height: Double) {
let side = min(width, height) // Неожиданное поведение!
super.setDimensions(width: side, height: side)
}
}
// Функция, работающая с базовым классом
func doubleWidth(of rectangle: Rectangle) {
rectangle.setDimensions(width: rectangle.width * 2,
height: rectangle.height) // Ожидается, что изменится только ширина.
}
let rect = Rectangle()
rect.setDimensions(width: 5, height: 4)
doubleWidth(of: rect)
print("Rectangle area: (rect.area)") // Ожидаемо: 5*2 * 4 = 40
let sq: Rectangle = Square() // Подстановка Square вместо Rectangle
sq.setDimensions(width: 5, height: 4)
doubleWidth(of: sq) // Внутри вызовется переопределенный метод Square
print("Square area (treated as Rectangle): (sq.area)") // Неожиданно: 4*4 = 16 (а не 40)!
Почему это нарушение: Код, использующий Rectangle, ожидает, что width и height изменяются независимо. Square ломает это предположение, что приводит к ошибкам. С точки зрения LSP, Square не является подтипом Rectangle в поведенческом смысле.
Пример соблюдения LSP
// Абстракция через протокол
protocol Shape {
var area: Double { get }
}
// Конкретные реализации, не связанные наследованием
struct RectangleShape: Shape {
let width, height: Double
var area: Double { width * height }
}
struct SquareShape: Shape {
let side: Double
var area: Double { side * side }
}
struct CircleShape: Shape {
let radius: Double
var area: Double { .pi * radius * radius }
}
// Функция работает с абстракцией Shape и корректна для любой подстановки.
func printArea(of shape: Shape) {
print("The area is (shape.area)")
}
let shapes: [Shape] = [RectangleShape(width: 5, height: 4),
SquareShape(side: 5),
CircleShape(radius: 3)]
for shape in shapes {
printArea(of: shape) // Всегда корректно, LSP соблюден.
}
Ключевой вывод: LSP поощряет проектирование через абстракции (протоколы/интерфейсы) и композицию, а не через глубокие иерархии наследования, которые могут нарушать поведенческие контракты.