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

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

1.0 Junior🔥 161 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP) — это один из пяти ключевых принципов SOLID в объектно-ориентированном программировании. Он был сформулирован Барбарой Лисков в 1987 году и гласит: «Если S является подтипом T, то объекты типа T могут быть заменены объектами типа S без нарушения корректности программы». На практике это означает, что наследники класса должны дополнять или специализировать поведение базового класса, но не изменять его семантику.

Ключевые идеи LSP

LSP направлен на обеспечение корректности наследования и предотвращение ошибок, которые возникают при нарушении контракта базового класса. Основные требования:

  • Наследник не должен укреплять предусловия (требования к входным параметрам) базового класса. Например, если базовый метод принимает любой Int, наследник не может требовать только положительные числа.
  • Наследник не должен ослаблять постусловия (гарантии после выполнения метода) базового класса. Например, если базовый метод гарантирует очистку ресурсов, наследник также должен это делать.
  • Наследник должен сохранять инварианты (внутренние состояния) базового класса. Например, если базовый класс гарантирует, что поле balance никогда отрицательно, наследник не может нарушать это правило.

Пример нарушения LSP

Рассмотрим классический пример с прямоугольниками и квадратами:

class Rectangle {
    var width: Double
    var height: Double
    
    init(width: Double, height: Double) {
        self.width = width
        self.height = height
    }
    
    func area() -> Double {
        return width * height
    }
}

class Square: Rectangle {
    override var width: Double {
        didSet {
            height = width // Нарушение инварианта: квадрат всегда требует width == height
        }
    }
    
    override var height: Double {
        didSet {
            width = height
        }
    }
    
    init(side: Double) {
        super.init(width: side, height: side)
    }
}

Проблема: Квадрат нарушает контракт прямоугольника, потому что у прямоугольника width и height независимы, а квадрат связывает их. Если клиентский код использует Rectangle и изменяет только ширину, ожидая, что высота останется неизменной, то с Square это приведёт к неожиданному поведению:

func resize(rectangle: Rectangle, newWidth: Double) {
    rectangle.width = newWidth
    print("Area after resize: \(rectangle.area())")
}

let square = Square(side:10)
resize(rectangle: square, newWidth:20) // Высота тоже станет 20, что нарушает ожидания клиента

Как соблюдать LSP в iOS разработке?

  1. Использовать протоколы (интерфейсы) для определения контрактов. В Swift LSP часто соблюдается через протоколы, которые четко задают требования:
protocol Shape {
    func area() -> Double
}

struct Rectangle: Shape {
    var width: Double
    var height: Double
    
    func area() -> Double {
        return width * height
    }
}

struct Square: Shape {
    var side: Double
    
    func area() -> Double {
        return side * side
    }
}
  1. Избегать наследования, если поведение существенно отличается. Вместо наследования использовать композицию или обобщённые типы.
  2. Тестировать замещаемость: при создании подкласса проверять, что он может использоваться во всех контекстах базового класса без сбоев.
  3. Следовать контракту методов: переопределяемые методы должны сохранять исходные предусловия, постусловия и не изменять типы параметров/возвращаемых значений.

Практическое значение в iOS

В iOS разработке LSP критически важен для:

  • Работы с UIKit/UIKit: например, создание собственных UIView или UITableViewCell, которые должны корректно замещать стандартные классы.
  • Архитектуры приложения: соблюдение LSP в слоях бизнес-логики или сервисах позволяет легко заменять модули (например, разные реализации DataProvider).
  • Тестирования: возможность использовать моки и стабы, которые замещают реальные объекты без нарушения контрактов.

Нарушение LSP приводит к нестабильности системы, сложностям в рефакторинге и непредсказуемым ошибкам. Соблюдение принципа обеспечивает расширяемость, читаемость и надёжность кода, что особенно важно в долгосрочной поддержке iOS приложений.