← Назад к вопросам
Что такое принцип Барбаре Лисков (Liskov Substitution Principle)?
2.0 Middle🔥 81 комментариев
#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Принцип Барбары Лисков (Liskov Substitution Principle)
Лисков Субституция (LSP) — третий принцип SOLID, сформулированный Барбарой Лисков в 1987 году. Звучит просто:
"Объекты подклассов должны корректно заменять объекты базовых классов без нарушения логики программы"
Другими словами: если класс S является подтипом класса T, то объекты типа T могут быть заменены объектами типа S без нарушения желаемых свойств программы.
Правило: замещаемость
# ✅ Хорошо — полиморфизм работает правильно
class Animal:
def make_sound(self) -> str:
raise NotImplementedError
class Dog(Animal):
def make_sound(self) -> str:
return "Woof!"
class Cat(Animal):
def make_sound(self) -> str:
return "Meow!"
def play_sound(animal: Animal):
# Здесь может быть любое Animal — работает LSP
print(animal.make_sound())
# Используем
dog = Dog()
cat = Cat()
play_sound(dog) # Woof!
play_sound(cat) # Meow!
# Обе замены работают правильно
Нарушение LSP — классический пример
# ❌ НАРУШЕНИЕ LSP
class Rectangle:
def __init__(self, width: int, height: int):
self._width = width
self._height = height
def set_width(self, width: int):
self._width = width
def set_height(self, height: int):
self._height = height
def get_area(self) -> int:
return self._width * self._height
# Квадрат является прямоугольником? Математически да...
class Square(Rectangle):
def set_width(self, width: int):
# Но для квадрата оба измерения должны быть одинаковы
self._width = width
self._height = width # 🚨 нарушаем ожидание от Rectangle
def set_height(self, height: int):
self._width = height
self._height = height
# Клиент ожидает поведение Rectangle
def test_rectangle(rect: Rectangle):
rect.set_width(5)
rect.set_height(3)
assert rect.get_area() == 15 # 5 * 3 = 15
# Подаём Square вместо Rectangle — УПАЛ ТЕСТ!
square = Square(0, 0)
test_rectangle(square)
# square.get_area() == 9, а не 15 (3 * 3) 🔴
Проблема: Square нарушает контракт Rectangle. Клиентский код ломается.
Как исправить — правильная иерархия
# ✅ ИСПРАВЛЕННЫЙ КОД
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def get_area(self) -> int:
pass
class Rectangle(Shape):
def __init__(self, width: int, height: int):
self.width = width
self.height = height
def get_area(self) -> int:
return self.width * self.height
class Square(Shape):
def __init__(self, side: int):
self.side = side
def get_area(self) -> int:
return self.side ** 2
# Теперь нет наследования Square от Rectangle
# Обе реализации удовлетворяют контракту Shape
shapes: list[Shape] = [
Rectangle(5, 3),
Square(4)
]
for shape in shapes:
print(shape.get_area()) # 15, 16 — всё правильно
Практический пример: Database коннектор
from abc import ABC, abstractmethod
class DatabaseConnection(ABC):
@abstractmethod
def connect(self):
pass
@abstractmethod
def execute(self, query: str):
pass
@abstractmethod
def close(self):
pass
class PostgresConnection(DatabaseConnection):
def connect(self):
print("Connecting to PostgreSQL...")
def execute(self, query: str):
print(f"Executing: {query}")
def close(self):
print("Closing PostgreSQL connection")
class MongoConnection(DatabaseConnection):
def connect(self):
print("Connecting to MongoDB...")
def execute(self, query: str):
print(f"Executing: {query}")
def close(self):
print("Closing MongoDB connection")
# Клиентский код не знает конкретной БД
class DataRepository:
def __init__(self, db: DatabaseConnection):
self.db = db
def save_user(self, user_data):
self.db.connect()
self.db.execute(f"INSERT INTO users VALUES {user_data}")
self.db.close()
# Используем с любой БД
postgres = PostgresConnection()
mongo = MongoConnection()
repo_pg = DataRepository(postgres)
repo_mongo = DataRepository(mongo)
# Обе работают одинаково для клиента
repo_pg.save_user({"name": "John"})
repo_mongo.save_user({"name": "John"})
Признаки нарушения LSP
# 1️⃣ Type-checking перед использованием
def process(animal: Animal):
if isinstance(animal, Dog):
# Специальная логика для Dog
...
elif isinstance(animal, Cat):
# Специальная логика для Cat
...
# 🚨 Это красный флаг!
# 2️⃣ Пустые реализации методов
class Bird(Animal):
def fly(self):
raise NotImplementedError("This bird cannot fly")
# 🚨 Нарушаем контракт
# 3️⃣ Усиление предусловий (требуем больше в подклассе)
class PaymentProcessor:
def process_payment(self, amount: float):
assert amount > 0 # предусловие
class SpecialPaymentProcessor(PaymentProcessor):
def process_payment(self, amount: float):
assert amount > 100 # 🚨 более сильное условие
# 4️⃣ Ослабление постусловий (гарантируем меньше в подклассе)
class Logger:
def log(self, message: str) -> str:
# Гарантирует, что вернёт логированное сообщение
return f"[LOG] {message}"
class SilentLogger(Logger):
def log(self, message: str) -> str:
return None # 🚨 нарушили постусловие
Как проверить соответствие LSP
# ✅ Правильный тест
def test_liskov_substitution():
animals: list[Animal] = [Dog(), Cat(), Bird()]
# Все работают с одним кодом
for animal in animals:
sound = animal.make_sound()
assert isinstance(sound, str)
assert len(sound) > 0
# Тест проходит — LSP соблюдается
# ❌ Тест провалится если подкласс нарушает LSP
def test_rectangle_square():
shapes: list[Rectangle] = [Rectangle(5, 3), Square(4)]
for shape in shapes:
shape.set_width(10)
shape.set_height(5)
assert shape.get_area() == 50 # FAIL для Square!
LSP — это гарант того, что полиморфизм работает правильно. Если соблюдать этот принцип, код становится предсказуемым и безопасным для расширения через наследование.