Каковы негативные аспекты наследования стека Java-класса от Vector? - PullRequest
17 голосов
/ 27 мая 2010

Расширив класс Vector, дизайнеры Java смогли быстро создать класс Stack. Что негативные аспекты этого использования наследования, особенно для класса Stack?

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

Ответы [ 6 ]

25 голосов
/ 27 мая 2010

Effective Java 2nd Edition, Item 16: Композиция Favor вместо наследования :

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

В библиотеках платформы Java существует ряд очевидных нарушений этого принципа. Например, стек не является вектором, поэтому Stack не должен расширяться Vector. Аналогично, список свойств не является хеш-таблицей, поэтому Properties не должен расширяться Hashtable. В обоих случаях состав был бы предпочтительнее.

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

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

    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"

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

Смотри также

22 голосов
/ 27 мая 2010

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

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

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

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

6 голосов
/ 27 мая 2010

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

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

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

3 голосов
/ 27 мая 2010

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

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

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

2 голосов
/ 27 мая 2010

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

1 голос
/ 27 мая 2010

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...