← Назад к вопросам

Что такое принцип подстановки Барбары Лисков?

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: все репозитории должны одинаково обрабатывать кеш и источники данных

Лиском — это не просто правило наследования, это контракт между базовым классом и его подклассами, обеспечивающий предсказуемость и надёжность кода.

Что такое принцип подстановки Барбары Лисков? | PrepBro