Каковы отрицательные аспекты Java-класса Stack, наследующего от Vector?
Расширяя класс Vector, дизайнеры Javas смогли быстро создать класс Stack. Что
отрицательные аспекты этого использования наследования, особенно для класса Stack?
Большое спасибо.
Ответы
Ответ 1
Одна проблема заключается в том, что Stack - это класс, а не интерфейс. Это расходится с дизайном рамки коллекции, где ваше существительное обычно представлено в виде интерфейса (например, List, Tree, Set и т.д.), И существуют определенные реализации (например, ArrayList, LinkedList). Если Java может избежать обратной совместимости, тогда более подходящий дизайн будет иметь интерфейс Stack, а VectorStack - как реализацию.
Вторая проблема заключается в том, что Stack теперь привязан к Vector, который обычно избегается в пользу ArrayLists и т.п.
Третья проблема заключается в том, что вы не можете легко реализовать свою собственную реализацию стека, а стеки поддерживают очень не стековые операции, такие как получение элемента из определенного индекса, в том числе возможность исключения индекса. Как пользователь, вам также может понадобиться знать, находится ли верхняя часть стека с индексом 0 или индексом n. Интерфейс также предоставляет детали реализации, такие как емкость.
Из всех решений в исходной библиотеке классов Java я считаю это одним из наиболее своеобразных. Я сомневаюсь, что Агрегация была бы намного дороже, чем наследование.
Ответ 2
Эффективное Java 2nd Edition, позиция 16: предпочтение композиции над наследованием:
Наследование подходит только в тех случаях, когда подкласс действительно является подтипом суперкласса. Другими словами, класс 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"
Это грубое нарушение типа абстрактных данных стека.
См. также
Ответ 3
Наличие подкласса Stack
Vector
предоставляет методы, которые не подходят для стека, потому что стек не является вектором (он нарушает Принцип замены Лискова).
Например, стек представляет собой структуру данных LIFO, но при использовании этой реализации вы можете вызвать методы elementAt
или get
для извлечения элемента по указанному индексу. Или вы можете использовать insertElementAt
, чтобы подорвать договор стека.
Я думаю, что Джошуа Блох записал, что наличие Stack
подкласса Vector
было ошибкой, но, к сожалению, я не могу найти ссылку.
Ответ 4
Ну, Stack
должен быть интерфейсом.
Интерфейс Stack
должен определять операции, которые может выполнять стек. Тогда могут быть разные реализации Stack
, которые выполняются по-разному в разных ситуациях.
Но, поскольку Stack
- это конкретный класс, этого не может произойти. Мы ограничены одной реализацией стека.
Ответ 5
В дополнение к основным действительным точкам, упомянутым выше, другая большая проблема с Stack, наследующим от Vector is Vector, полностью синхронизирована, поэтому вы получаете эту накладную стоимость, если вам это нужно или нет (см. StringBuffer vs. StringBuilder). Лично я склонен использовать ArrayDeque всякий раз, когда хочу стек.
Ответ 6
Это нарушает самое первое правило, которое мы все узнали о наследовании: можете ли вы с прямым лицом сказать, что Stack IS-A Vector? Ясно, что нет.
Еще одна логическая операция заключалась бы в использовании агрегации, но лучшим вариантом ИМО было бы сделать Stack интерфейсом, который может быть реализован любой соответствующей структурой данных, подобной (но не совсем такой же), что и С++ STL делает.