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

Что такое LSP?

2.0 Middle🔥 81 комментариев
#Архитектура и паттерны#ООП

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

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

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

LSP (Liskov Substitution Principle) — Принцип подстановки Барбары Лисков

LSP — один из пяти принципов SOLID, который гласит: объекты подклассов должны корректно заменять объекты базовых классов без нарушения корректности программы. Проще говоря, если класс B наследует от класса A, то везде, где используется A, можно безопасно подставить B.

Суть принципа

// ❌ Нарушение LSP
abstract class Bird {
    abstract public function fly();
}

class Sparrow extends Bird {
    public function fly() {
        return "Sparrow flies";
    }
}

class Penguin extends Bird {
    public function fly() {
        throw new Exception("Penguin cannot fly!");
    }
}

// Проблема: код, работающий с Bird, сломается на Penguin
function makeBirdFly(Bird $bird) {
    return $bird->fly(); // Взлетит исключение для пингвина!
}

Правильное решение:

// ✅ Соблюдение LSP
interface Bird {
    public function move();
}

class FlyingBird implements Bird {
    public function move() {
        return "Flying";
    }
}

class SwimmingBird implements Bird {
    public function move() {
        return "Swimming";
    }
}

class Sparrow extends FlyingBird {}
class Penguin extends SwimmingBird {}

// Теперь все Bird могут быть подставлены безопасно
function makeBirdMove(Bird $bird) {
    return $bird->move(); // Работает для всех!
}

Ключевые правила LSP

  • Не усиливай предусловия — метод подкласса должен принимать не менее общие данные, чем у родителя
  • Не ослабляй постусловия — метод подкласса должен возвращать не менее конкретный результат
  • Не выбрасывай новые исключения — подкласс не должен выбрасывать исключения, которые родитель не выбрасывает
  • Сохраняй инварианты — ограничения, истинные для родителя, остаются истинными для подкласса

Пример с коллекциями

// ❌ Нарушение LSP
class Stack extends Array {
    public function push($value) {
        // переопределение поведения
    }
    
    public function pop() {
        // неожиданное поведение
    }
}

// Если код ожидает Array, он сломается на Stack

// ✅ Правильно — использование интерфейсов
interface Collection {
    public function add($item);
    public function remove();
}

class Stack implements Collection {
    private array $items = [];
    
    public function add($item) {
        $this->items[] = $item;
    }
    
    public function remove() {
        return array_pop($this->items);
    }
}

class Queue implements Collection {
    private array $items = [];
    
    public function add($item) {
        $this->items[] = $item;
    }
    
    public function remove() {
        return array_shift($this->items);
    }
}

// Обе коллекции могут использоваться везде
function processCollection(Collection $c) {
    $c->add("item");
    return $c->remove();
}

Преимущества LSP

  • Предсказуемость — код работает ожидаемо для всех подтипов
  • Гибкость — легко добавлять новые реализации без изменения клиентского кода
  • Надежность — меньше скрытых ошибок и неожиданного поведения
  • Тестируемость — проще юнит-тестировать полиморфный код

LSP в реальном коде

// ✅ Хороший пример из реальных проектов
interface PaymentGateway {
    public function charge(float $amount): bool;
}

class StripeGateway implements PaymentGateway {
    public function charge(float $amount): bool {
        // Stripe логика
        return true;
    }
}

class PayPalGateway implements PaymentGateway {
    public function charge(float $amount): bool {
        // PayPal логика
        return true;
    }
}

class PaymentService {
    public function __construct(private PaymentGateway $gateway) {}
    
    public function processPayment(float $amount): bool {
        return $this->gateway->charge($amount);
    }
}

// Можно подставить любой шлюз, код работает одинаково
$service = new PaymentService(new StripeGateway());
// или
$service = new PaymentService(new PayPalGateway());
Что такое LSP? | PrepBro