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

Приведи пример нарушения Liskov Substitution Principle из SOLID в стандартных библиотеках

3.0 Senior🔥 21 комментариев
#SOLID и паттерны проектирования#ООП

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

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

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

Liskov Substitution Principle нарушение в стандартных библиотеках

Что такое Liskov Substitution Principle

Liskov Substitution Principle (LSP) — принцип, который гласит, что объекты подклассов должны корректно заменять объекты базовых классов без нарушения корректности программы.

Другими словами: если класс B наследует класс A, то везде, где используется A, можно подставить B без неожиданных последствий.

Классический пример нарушения: java.util.Stack

java.util.Stack наследует java.util.Vector. Это нарушает LSP.

// Stack наследует Vector
public class Stack<E> extends Vector<E> {
    // ...
}

// Проблема:
Vector<Integer> vector = new Stack<>();

// Vector позволяет вставлять элементы в середину:
vector.insertElementAt(100, 0);  // OK для Vector

// Но для Stack это странно — мы нарушаем LIFO (Last In First Out) семантику!
// Stack должен работать только с push/pop, а Vector позволяет произвольный доступ

Почему это нарушение LSP

  1. Vector — это структура данных с произвольным доступом к элементам
  2. Stack — это LIFO структура, где доступ только к верхушке
  3. Когда Stack наследует Vector, клиент может:
    • Вызвать vector.insertElementAt() на Stack
    • Сломать инвариант Stack (LIFO порядок)
// Контракт Stack нарушен:
Stack<String> stack = new Stack<>();
stack.push("A");      // OK
stack.push("B");      // OK
stack.push("C");      // OK

// Но Vector методы позволяют:
stack.insertElementAt("X", 0);  // Нарушает LIFO!

String top = stack.pop();  // Вернёт 'C', но теперь Stack содержит 'X', 'A', 'B'
// Мы ожидали 'C', но поведение неправильно

Ещё один пример: java.util.Properties

java.util.Properties наследует java.util.Hashtable.

// Properties предназначен для String ключей и значений
public class Properties extends Hashtable<Object, Object> {
    // ...
}

// Но благодаря наследованию от Hashtable:
Properties props = new Properties();
props.put(123, 456);          // Типично для Hashtable
props.put("key", "value");   // Типично для Properties

// Теперь props содержит не только String-String пары!
// Это нарушает контракт Properties

// Проблема проявляется при сохранении:
props.store(outputStream, "Comment");  // Ожидает String ключи/значения
// Но может получить Integer, что приведёт к непредсказуемому поведению

Почему это произошло в Java

Это исторический артефакт:

  • Java 1.0 был создан с недостаточным пониманием SOLID принципов
  • Stack и Properties были реализованы через наследование для переиспользования кода
  • Это была ошибка дизайна, которую сложно исправить без breaking changes

Правильный подход

Вместо наследования нужно использовать композицию:

// Правильно: Stack содержит List, не наследует Vector
public class Stack<E> {
    private List<E> elements = new ArrayList<>();
    
    public void push(E element) {
        elements.add(element);
    }
    
    public E pop() {
        if (isEmpty()) throw new EmptyStackException();
        return elements.remove(elements.size() - 1);
    }
    
    public boolean isEmpty() {
        return elements.isEmpty();
    }
}

// Правильно: Properties не наследует Hashtable
public final class Properties {
    private final Map<String, String> properties = new HashMap<>();
    
    public String getProperty(String key) {
        return properties.get(key);
    }
    
    public void setProperty(String key, String value) {
        properties.put(key, value);
    }
}

Вывод

Liskov Substitution Principle нарушается в java.util.Stack и java.util.Properties из-за неправильного выбора наследования вместо композиции. Это классический пример того, как наследование может усложнить код вместо упрощения. В современной Java лучше использовать композицию и явно определять интерфейсы, которые соответствуют контракту класса.