Приведите пример нарушения принципа подстановки Барбары Лисков (LSP) в стандартных библиотеках Java.

Ответ

Принцип подстановки Лисков (LSP): Объекты базового класса должны быть заменяемы объектами производного класса без изменения корректности программы.

Классическое нарушение в Java: Класс java.util.Stack<E>, который наследуется от java.util.Vector<E>.

Почему это нарушение LSP? Stack по своей семантике — структура данных LIFO (Last-In-First-Out). Его публичный контракт — операции push, pop, peek. Наследуясь от Vector, он «наследует» множество публичных методов, которые нарушают этот контракт и позволяют модифицировать стек произвольным образом.

import java.util.Stack;

public class LSPViolationExample {
    public static void main(String[] args) {
        Stack<String> stack = new Stack<>();
        stack.push("First"); // Корректная операция стека
        stack.push("Second");

        // НАРУШЕНИЕ LSP: Методы, унаследованные от Vector,
        // ломают инварианты стека (доступ к произвольному элементу).
        stack.insertElementAt("Intruder", 0); // Метод из Vector!
        stack.remove(1);                      // Метод из Vector!

        // Теперь поведение pop() не соответствует LIFO.
        System.out.println(stack.pop()); // Что будет выведено? Неочевидно.
    }
}

Следствия нарушения:

  • Ненадежность: Клиентский код, использующий Stack как Vector, может сломать логику стека.
  • Нарушение инкапсуляции: Внутреннее состояние (порядок элементов) можно изменить в обход основных методов.
  • Невозможность безопасной замены: Stack не может быть использован везде, где ожидается Vector, без риска ошибок.

Правильная реализация (соответствующая LSP) должна была использовать композицию: хранить Vector или ArrayList как приватное поле и предоставлять только методы стека, делегируя им вызовы к внутренней коллекции.

Ответ 18+ 🔞

Ну вот, опять эти ваши принципы, блядь. Солид, SOLID, как там его... А четвёртая буква — это как раз про Лисков, сука. И звучит она умно: «Объекты базового класса должны быть заменяемы объектами производного класса без изменения корректности программы». Ну, вроде логично, да? Ага, щас.

А теперь смотри сюда, на эту классику жанра, на этот пиздец, который прямо в ядре Java засел, как заноза в жопе. java.util.Stack. Помнишь его? Стек, LIFO, последний зашёл — первый вышел. Всё просто, как три копейки.

Так вот, блядь, создатели Java, видимо, в тот день ебали мозг друг другу, а не думали. Потому что они взяли и наследовали этот Stack от java.util.Vector. Ну, типа, вектор — это список с доступом по индексу, а стек — это типа частный случай, да? Ага, частный случай, который ломает всю логику нахуй!

Смотри, в чём трагедия, блядь. У стека контракт простой: push, pop, peek. Засунул, вынул, посмотрел верхушку. Всё, пиздец. А что он получает от папочки Vector? Овердохуище методов! insertElementAt, remove, get по любому индексу! Это ж как, понимаешь? Тебе дали сейф, а к нему прилагается отмычка на все замки и инструкция, как его взорвать. Ну нахуя?

import java.util.Stack;

public class LSPViolationExample {
    public static void main(String[] args) {
        Stack<String> stack = new Stack<>();
        stack.push("First"); // Нормально, по-человечески
        stack.push("Second"); // Тоже ок

        // А ТУТ НАЧИНАЕТСЯ ЦИРК, БЛЯДЬ!
        // Это методы от Vector, они тут как гандон на свадьбе.
        stack.insertElementAt("Intruder", 0); // Засунул хуй знает куда!
        stack.remove(1);                      // Выдрал середину нахуй!

        // И теперь попробуй угадай, что вынет pop()?
        // Первый? Второй? Интрудера? Хуй его знает! Семантика LIFO накрылась медным тазом.
        System.out.println(stack.pop()); // Сюрприз, сука!
    }
}

Вот и получается, что Stack — это НЕ Vector. Его нельзя воткнуть туда, где ждут Vector, потому что он начнёт творить дичь. Клиентский код, который работает с Vector, может спокойно вызвать insertElementAt и даже не подозревать, что он только что сломал священный принцип стека. Инкапсуляция? Да её тут и не пахнет, блядь! Внутреннее состояние объекта можно ебашить со всех сторон, как тупую мартышку.

А правильный-то путь какой был, ёпта? Да ебаный рот, композиция! Надо было не наследоваться от Vector, а содержать его внутри, как приватное поле. И уже к этому полю делегировать только те операции, которые нужны стеку. push -> add, pop -> remove(last). И всё, пиздец. Никаких лишних дырок в контракте. Тогда бы Stack был настоящим стеком, а не этим уродцем-полукровкой.

Вот так вот, блядь. Даже в стандартной библиотеке таких граблей наставлено — овердохуища. Учись на чужих косяках, а то сам такого наворотишь, потом будешь глазами хлопать, как Герасим, и мычать «Му-му», блядь.