Аргумент «ужасно для памяти» совершенно неверен, но это объективно «плохая практика». Когда вы наследуете от класса, вы не просто наследуете поля и методы, которые вас интересуют. Вместо этого вы наследуете все . Каждый метод, который он объявляет, даже если он бесполезен для вас. И самое главное, вы также наследуете все его контракты и гарантии, которые предоставляет класс.
Акроним SOLID обеспечивает некоторую эвристику для хорошего объектно-ориентированного дизайна. Здесь я nterface Сегрегация принцип (ISP) и L Иськов Замена Pricinple (LSP) есть что - то сказать.
Интернет-провайдер говорит нам, чтобы наши интерфейсы были как можно меньше. Но, наследуя от ArrayList
, вы получаете много-много методов. Есть ли смысл get()
, remove()
, set()
(заменить), или add()
(вставка) дочерний узел с определенным индексом? Разумно ли это ensureCapacity()
из основного списка? Что это значит для sort()
узла? Пользователи вашего класса действительно должны получить subList()
? Поскольку вы не можете скрыть ненужные методы, единственное решение состоит в том, чтобы ArrayList являлся переменной-членом и пересылать все методы, которые вам действительно нужны:
private final ArrayList<Node> children = new ArrayList();
public void add(Node child) { children.add(child); }
public Iterator<Node> iterator() { return children.iterator(); }
Если вам действительно нужны все методы, которые вы видите в документации, мы можем перейти к LSP. LSP говорит нам, что мы должны иметь возможность использовать подкласс везде, где ожидается родительский класс. Если функция принимает в ArrayList
качестве параметра, и мы Node
вместо этого передаем наш , ничего не должно измениться.
Совместимость подклассов начинается с простых вещей, таких как сигнатуры типов. Когда вы переопределяете метод, вы не можете сделать типы параметров более строгими, поскольку это может исключить использование, допустимое для родительского класса. Но это то, что компилятор проверяет нас в Java.
Но LSP работает гораздо глубже: мы должны поддерживать совместимость со всем, что обещано документацией всех родительских классов и интерфейсов. В своем ответе , Линн обнаружила один такой случай , когда List
интерфейс (который вы унаследовали через ArrayList
) гарантирует , как equals()
и hashCode()
методы должны работать. Для hashCode()
вас даже дан определенный алгоритм, который должен быть реализован точно. Давайте предположим, что вы написали это Node
:
public class Node extends ArrayList<Node> {
public final int value;
public Node(int value, Node... children) {
this.value = Value;
for (Node child : children)
add(child);
}
...
}
Это требует, чтобы value
не мог внести свой вклад hashCode()
и не может влиять equals()
. List
Интерфейс - что вы обещаете честь, унаследовав от него - требует , new Node(0).equals(new Node(123))
чтобы быть правдой.
Поскольку наследование от классов позволяет слишком легко случайно нарушить обещание, данное родительским классом, и поскольку оно обычно предоставляет больше методов, чем вы предполагали, обычно рекомендуется выбирать композицию вместо наследования . Если вы должны что-то наследовать, предлагается наследовать только интерфейсы. Если вы хотите повторно использовать поведение определенного класса, вы можете сохранить его как отдельный объект в переменной экземпляра, чтобы все его обещания и требования не были навязаны вам.
Иногда наш естественный язык предполагает наследственные отношения: автомобиль - это транспортное средство. Мотоцикл - это транспортное средство. Должен ли я определить классы Car
и Motorcycle
что наследовать от Vehicle
класса? Объектно-ориентированный дизайн - это не отражение реального мира именно в нашем коде. Мы не можем легко закодировать богатые таксономии реального мира в нашем исходном коде.
Одним из таких примеров является проблема моделирования сотрудник-босс. У нас есть несколько Person
s, каждый с именем и адресом. An Employee
есть Person
и имеет Boss
. А Boss
также Person
. Так я должен создать Person
класс, который наследуется Boss
и Employee
? Теперь у меня есть проблема: начальник - тоже работник и другой начальник. Так что вроде как Boss
должно расширяться Employee
. Но CompanyOwner
это, Boss
но не Employee
? Любой вид графа наследования здесь как-то сломается.
ООП - это не иерархии, наследование и повторное использование существующих классов, а обобщение поведения . ООП - это «У меня есть куча объектов, и я хочу, чтобы они выполняли определенную работу, и мне все равно, как». Вот для чего нужны интерфейсы . Если вы реализуете Iterable
интерфейс для своего, Node
потому что хотите сделать его итеративным, это прекрасно. Если вы реализуете Collection
интерфейс, потому что хотите добавить / удалить дочерние узлы и т. Д., Тогда это нормально. Но наследование от другого класса, потому что это дает вам все, чего нет, или, по крайней мере, нет, если вы не продумали его тщательно, как описано выше.