Ответ
Принцип подстановки Лисков (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 поощряет проектирование через абстракции (протоколы/интерфейсы) и композицию, а не через глубокие иерархии наследования, которые могут нарушать поведенческие контракты.
Ответ 18+ 🔞
Да ты посмотри, на что они нас, программистов, подписывают! Принцип подстановки Лисков, блядь. Звучит-то как научно, а по сути — элементарная вещь, которую любой мудак, накосячивший с наследованием, потом осознаёт через боль и страдания.
Вот смотри, суть в чём: если у тебя есть какой-то базовый тип, скажем, Птица, и от него наследуется Пингвин, то в любом месте кода, где ожидается Птица, ты должен суметь подсунуть Пингвина, и всё должно работать, а не накрыться медным тазом. Не должно программа от этого вздрачивать и выёбываться.
А теперь реальность, блядь! Берут классический пример — прямоугольник и квадрат. Ну, в геометрии-то квадрат — частный случай прямоугольника, да? А в коде — пиздец и развод.
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
}
}
Всё чинно, благородно. Прямоугольник. Ширину и высоту задал — площадь посчитал. Контракт простой: width и height живут своей жизнью.
А теперь какой-то умник думает: «А давайте я сделаю класс Square, который наследуется от Rectangle. Это же логично!». И начинает творить дичь:
class Square: Rectangle {
override func setDimensions(width: Double, height: Double) {
let side = min(width, height) // Неожиданное поведение!
super.setDimensions(width: side, height: side)
}
}
Вот тут-то и начинается, ёпта! Родитель-то обещал, что setDimensions установит ровно то, что ему передали. А этот выродок-квадрат берёт и сам решает, какую сторону использовать! Он контракт родителя в пизду выкинул.
И вот представь, есть у тебя функция, которая работает с прямоугольником:
func doubleWidth(of rectangle: Rectangle) {
rectangle.setDimensions(width: rectangle.width * 2,
height: rectangle.height) // Ожидается, что изменится только ширина.
}
Всё ок, работает. Берём прямоугольник 5x4, удваиваем ширину — площадь становится 40. Красота.
А теперь подсовываем квадрат, замаскированный под прямоугольника:
let sq: Rectangle = Square() // Подстановка Square вместо Rectangle
sq.setDimensions(width: 5, height: 4) // Установится сторона 4, ёбана!
doubleWidth(of: sq) // Вызовется переопределённый метод Square! Он опять возьмёт минимум!
print("Square area (treated as Rectangle): (sq.area)") // Блядь, 16! А где 40?!
Вот тебе и «частный случай прямоугольника». Нахуй он никому не сдался в таком виде. Программа ебнулась, потому что квадрат нарушил все ожидания. Он не является (в поведенческом смысле!) подтипом прямоугольника. Он — хитрая жопа, которая притворяется.
Так как же правильно, блядь? Да не лепить иерархию наследования там, где она не работает! Использовать абстракцию, ёпта!
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 }
}
Смотри, какая красота! Никакого наследования, только протокол. У каждой фигуры есть площадь, и каждая сама решает, как её считать. Никаких скрытых пиздецов.
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 не просто соблюдён, он ликует!
}
Вывод, сука: Этот принцип — не про геометрию или бизнес-логику. Он про то, чтобы твои абстракции не были пиздопроебибными. Проектируй через интерфейсы (протоколы), а не через кривое наследование. И тогда подставляй что угодно — будет работать, а не ебать мозг тебе и всем, кто будет читать твой код после.