Какой принцип SOLID нарушается при добавлении нового метода в унаследованный класс?

Ответ

Такое изменение может нарушить Принцип подстановки Барбары Лисков (Liskov Substitution Principle, LSP).

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

Нарушение при добавлении метода:

  1. Сужение предусловий: Новый метод в подклассе может накладывать более строгие условия для работы, чем ожидается от родителя.
  2. Расширение постусловий: Метод может гарантировать меньше, чем метод родителя.
  3. Нарушение инвариантов: Метод может изменять состояние объекта способом, недопустимым для родительского класса.

Классический пример нарушения LSP:

class Rectangle {
    protected int width, height;
    public void setWidth(int w) { width = w; }
    public void setHeight(int h) { height = h; }
    public int getArea() { return width * height; }
}

class Square extends Rectangle {
    // Нарушение LSP: изменяет контракт методов setWidth/setHeight.
    @Override
    public void setWidth(int w) {
        super.setWidth(w);
        super.setHeight(w); // Неожиданное побочное действие!
    }
    @Override
    public void setHeight(int h) {
        super.setHeight(h);
        super.setWidth(h); // Неожиданное побочное действие!
    }
}

// Клиентский код, работающий с Rectangle, сломается для Square.
void testArea(Rectangle r) {
    r.setWidth(5);
    r.setHeight(4);
    assert r.getArea() == 20; // Успех для Rectangle, провал для Square (получится 16).
}

Вывод: Добавление метода, которое меняет семантику или инварианты унаследованного поведения, нарушает LSP. Часто это сигнал к пересмотру иерархии наследования в пользу композиции.

Ответ 18+ 🔞

Да ты посмотри, какая тонкая хуйня всплывает! Вроде просто метод добавил в наследника — ну и что? А нихуя! Это же прямое нарушение Принципа подстановки Барбары Лисков, ёпта!

В чём соль, блядь: Если у тебя есть родительский класс, то на любой его объект должен спокойно садиться любой объект-потомок, и программа не должна обосраться. Потомок должен вести себя в рамках контракта родителя, а не выёбываться.

А как можно наебнуться, добавив метод?

  1. Ужесточить входные требования (предусловия). Родитель метод принимал int, а ты в наследнике говоришь: «А мне только положительные!». Клиентский код, рассчитанный на родителя, подаст тебе отрицательное — и ты ему в ответ исключение в ебальник. Непорядок.
  2. Ослабить гарантии (постусловия). Родительский метод клялся, что после его вызова объект будет в валидном состоянии. А твой новый метод в наследнике может этот инвариант разъебать.
  3. Просто сломать инварианты класса. Это когда твой новый метод начинает менять внутреннее состояние так, как родитель себе никогда не позволял.

Вот тебе классический пиздец на примере, прямо в рот:

class Rectangle { // Прямоугольник, блядь
    protected int width, height;
    public void setWidth(int w) { width = w; }
    public void setHeight(int h) { height = h; }
    public int getArea() { return width * height; }
}

class Square extends Rectangle { // Квадрат, сука
    // А вот тут начинается пиздец. Нарушаем LSP напрочь.
    @Override
    public void setWidth(int w) {
        super.setWidth(w);
        super.setHeight(w); // АХА! Нежданчик, блядь! Меняю и высоту тоже!
    }
    @Override
    public void setHeight(int h) {
        super.setHeight(h);
        super.setWidth(h); // И тут то же самое, ёбта!
    }
}

// А теперь смотри, как клиентский код, который работал с Rectangle, ебётся:
void testArea(Rectangle r) {
    r.setWidth(5);
    r.setHeight(4);
    assert r.getArea() == 20; // Для прямоугольника — ок. Для квадрата — ПИЗДЕЦ! Получится 16, и тест провалится.
}

Вывод, блядь: Если добавление метода в наследнике ведёт к тому, что он начинает себя вести не так, как от него ожидают исходя из типа родителя — это пиздец и нарушение LSP. Часто такой сигнал кричит: «Бро, перестань ебаться с наследованием, может, тут композиция нужна?».