← Назад к вопросам
Что такое принцип подстановки Барбары Лисков?
1.0 Junior🔥 101 комментариев
#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Принцип подстановки Барбары Лисков (LSP)
Лиском (Liskov Substitution Principle) — один из пяти принципов SOLID. Он гласит: объекты подклассов должны корректно заменять объекты базовых классов без нарушения логики программы. Другими словами, если класс B наследует класс A, то везде, где используется A, можно подставить B, и программа должна работать правильно.
Суть принципа
Лиском требует, чтобы подкласс не усиливал предусловия и не ослаблял постусловия методов базового класса. Это гарантирует, что код, работающий с базовым типом, корректно работает и с подтипами.
Правила:
- Подкласс не может требовать больше ограничений на входные параметры
- Подкласс не может гарантировать меньше свойств возвращаемого значения
- Подкласс не должен выбрасывать новые checked исключения
- Инварианты базового класса должны сохраняться
Примеры нарушения и соблюдения
// ❌ НАРУШЕНИЕ LSP
open class Rectangle {
open var width: Int = 0
open var height: Int = 0
open fun getArea(): Int = width * height
}
class Square(size: Int) : Rectangle() {
init {
width = size
height = size
}
// Квадрат усиливает предусловие: требует width == height
override var width: Int
get() = super.width
set(value) {
super.width = value
super.height = value // Нарушает инвариант Rectangle
}
}
// Код, ожидающий Rectangle, сломается с Square
fun processRectangle(rect: Rectangle) {
rect.width = 5
rect.height = 10
assert(rect.getArea() == 50) // Для Square результат 100!
}
// ✅ СОБЛЮДЕНИЕ LSP
interface Shape {
fun getArea(): Double
}
class Rectangle(val width: Double, val height: Double) : Shape {
override fun getArea(): Double = width * height
}
class Square(val size: Double) : Shape {
override fun getArea(): Double = size * size
}
fun calculateTotalArea(shapes: List<Shape>): Double {
return shapes.sumOf { it.getArea() }
}
// Обе фигуры корректно заменяют Shape
val area = calculateTotalArea(listOf(
Rectangle(5.0, 10.0),
Square(5.0)
))
Применение в Android
- RecyclerView.Adapter: подклассы должны сохранять контракт getItemCount(), onCreateViewHolder()
- Service: все сервисы должны корректно обрабатывать onBind(), onStartCommand()
- LiveData<T>: наблюдатели ожидают определённого поведения при изменениях
- Repository Pattern: все репозитории должны одинаково обрабатывать кеш и источники данных
Лиском — это не просто правило наследования, это контракт между базовым классом и его подклассами, обеспечивающий предсказуемость и надёжность кода.