Каковы отрицательные аспекты класса Java Стек, наследовавшийся Вектору?

Путем расширения класса Вектор разработчики Java смогли создать класс Стек быстро. Каковы отрицательные аспекты этого использования наследования, особенно для класса Стек?

Большое спасибо.

17
задан Daniel Hershcovich 21 December 2011 в 06:52
поделиться

6 ответов

Одна проблема в том, что Stack - это класс, а не интерфейс. Это отличается от дизайна структуры коллекции, где ваше существительное обычно представлено как интерфейс (например, List, Tree, Set и т. Д.), И существуют определенные реализации (например, ArrayList, LinkedList). Если бы Java могла избежать обратной совместимости, то более правильным дизайном было бы иметь интерфейс Stack, а затем VectorStack в качестве реализации.

Вторая проблема заключается в том, что Stack теперь привязан к Vector, чего обычно избегают в пользу ArrayLists и т.п.

Третья проблема заключается в том, что вы не можете легко предоставить свою собственную реализацию стека, и что стеки поддерживают очень не-стековые операции, такие как получение элемента из определенного индекса, включая возможность исключений индекса. Как пользователь, вам также может потребоваться знать, находится ли вершина стека в индексе 0 или в индексе n. Интерфейс также раскрывает детали реализации, такие как емкость.

Из всех решений исходной библиотеки классов Java я считаю это одним из наиболее необычных. Сомневаюсь, что агрегирование было бы намного дороже наследования.

22
ответ дан 30 November 2019 в 10:18
поделиться

Наличие подкласса Stack Vector предоставляет методы, которые не подходят для стека, поскольку стек не является вектором (это нарушает Принцип замены Лискова ) .

Например, стек - это структура данных LIFO, но с помощью этой реализации вы можете вызвать методы elementAt или get для получения элемента по указанному индексу. Или вы можете использовать insertElementAt , чтобы нарушить контракт стека.

Я думаю, что Джошуа Блох официально заявил, что наличие подкласса Stack Vector было ошибкой, но, к сожалению, я не могу найти ссылку.

6
ответ дан 30 November 2019 в 10:18
поделиться

В дополнение к основным действительным пунктам, упомянутым выше, еще одна большая проблема со стеком, унаследованным от Vector, заключается в том, что Vector полностью синхронизирован, поэтому вы получаете эти накладные расходы независимо от того, нужны они вам или нет (см. StringBuffer vs. StringBuilder). Лично я обычно использую ArrayDeque всякий раз, когда мне нужен стек.

2
ответ дан 30 November 2019 в 10:18
поделиться

Ну, Stack должен быть интерфейсом.

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

Но, поскольку Stack является конкретным классом, этого не может произойти. Мы ограничены одной реализацией стека.

3
ответ дан 30 November 2019 в 10:18
поделиться

Эффективное 2-е издание Java, пункт 16: Предпочтение композиции перед наследованием :

Наследование уместно только в тех случаях, когда подкласс действительно является подтипом суперкласс.Другими словами, класс B должен расширять только класс A только в том случае, если между двумя классами существует связь «есть». Если вы хотите, чтобы класс B расширил класс A , задайте себе следующий вопрос: действительно ли каждый B является A ? Если вы не можете честно ответить утвердительно на этот вопрос, B не следует расширять A . Если ответ отрицательный, часто бывает так, что B должен содержать частный экземпляр A и предоставлять меньший и более простой API; A не является неотъемлемой частью B , это просто деталь его реализации.

Есть ряд очевидных нарушений этого принципа в библиотеках платформы Java. Например, стек не является вектором, поэтому Стек не должен расширять вектор . Точно так же список свойств не является хеш-таблицей, поэтому Свойства не должны расширять Hashtable . В обоих случаях композиция была бы предпочтительнее.

Книга содержит более подробные сведения и в сочетании с Правилом 17: Дизайн и документ для наследования или же запретить его , советует против чрезмерного использования и злоупотребления наследованием в вашем дизайне.

Вот простой пример, показывающий проблему стека , допускающую не- стек -подобное поведение:

    Stack<String> stack = new Stack<String>();
    stack.push("1");
    stack.push("2");
    stack.push("3");
    stack.insertElementAt("squeeze me in!", 1);
    while (!stack.isEmpty()) {
        System.out.println(stack.pop());
    }
    // prints "3", "2", "squeeze me in!", "1"

Это грубое нарушение абстрактного типа данных стека.

См. Также

25
ответ дан 30 November 2019 в 10:18
поделиться

Это нарушает самое первое правило, которое мы все узнали о наследовании: можете ли вы с невозмутимым видом сказать, что стек - это вектор? Ясно, что нет.

Другой более логичной операцией было бы использование агрегации, но лучшим вариантом IMO было бы сделать Stack интерфейсом, который мог бы быть реализован любой соответствующей структурой данных, подобной (но не точно такой же) тому, что C ++ STL делает.

2
ответ дан 30 November 2019 в 10:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: