Наследование Java и инициализация

Я читаю Дж. Блоха Эффективную Java, и теперь я нахожусь в разделе наследования и композиции. Насколько я понял, он сказал, что наследование не всегда хорошо.

Связанная причина хрупкости в подклассах заключается в том, что их суперкласс могут приобретать новые методы в последующих выпусках. Предположим, что программа зависит от его безопасности от того, что все элементы, вставленные в некоторые коллекции удовлетворяют некоторому предикату. Это может быть гарантировано на подклассификация коллекции и переопределение каждого метода, способного добавление элемента, чтобы убедиться, что предикат выполняется до добавление элемента. Это отлично работает до нового метода, способного добавление элемента добавляется в суперкласс в следующем выпуск.

Но почему это не работает? Суперкласс - это просто интерфейс Collection, и если мы добавим новый метод, мы просто ошибка времени компиляции. Это не вредно когда-либо...

Ответ 1

Предположим, у вас есть суперкласс класса в некоторой библиотеке v1.0:

public class MyCollection {
    public void add(String s) {
        // add to inner array
    }
}

Вы подклассифицируете его, чтобы принимать только строки, длина которых 5:

public class LimitedLengthCollection extends MyCollection {
    @Override
    public void add(String s) {
        if (s.length() == 5) {
            super.add(s);
        }
    }
}

Контракт, инвариант этого класса состоит в том, что он никогда не будет содержать строку, которая не имеет длины 5.

Теперь выпущена версия 2.0 библиотеки, и вы начнете ее использовать. Базовый класс изменяется на:

public class MyCollection {
    public void add(String s) {
        // add to inner array
    }

    public void addMany(String[] s) {
        // iterate on each element and add it to inner array
    }
}

и ваш подкласс оставлен без изменений. Теперь пользователи вашего подкласса могут выполнять

LimitedLengthCollection c = new LimitedLengthCollection();
c.addMany(new String[] {"a", "b", "c"});

и поэтому контракт вашего подкласса нарушен. Предполагалось, что он принимает только строки длиной 5, и это больше не происходит, потому что в суперкласс добавлен дополнительный метод.

Ответ 2

Проблема заключается не в том, что наследование не может работать.

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

Когда мы создаем новый класс редко, он действительно является специализированным типом другого. Чаще всего это нечто новое, которое использует другие классы.

Так редко нам нужно наследование, и чаще всего нам нужно создать класс, который использует другие классы для чего-то.

IS A vs HAS A

Вы должны спросить себя:

Класс B IS Новый подтип класса A, который выполняет одни и те же функции A по-разному?

или

Класс B HAS Класс внутри, чтобы сделать что-то отличное от что А намерено делать?

И знайте, что чаще всего правильный ответ на последний.

Ответ 3

Потому что он (в общем) разбивает клиентский код, который реализовал класс Collection.

В этом конкретном примере безопасность будет нарушена, так как вредоносные пользователи смогут вставлять элементы с помощью метода, но не переопределенного, который был добавлен после отправки вашего кода.

Основываясь на вашем коде наследования классов, которые вы не контролируете, могут укусить вас в будущем.

Ответ 4

если мы добавим новый mehtod, мы просто ошибка времени компиляции

Это верно только тогда, когда в суперкласс/интерфейс добавлен абстрактный метод. Если добавлен неабстрактный метод, это совершенно верно, чтобы не переопределять этот новый метод.