O argumento “horrível para a memória” está totalmente errado, mas é objetivamente uma “má prática”. Quando você herda de uma classe, não herda apenas os campos e métodos nos quais está interessado. Em vez disso, herda tudo . Todo método declarado, mesmo que não seja útil para você. E o mais importante, você também herda todos os seus contratos e garante que a classe fornece.
O acrônimo SOLID fornece algumas heurísticas para um bom design orientado a objetos. Aqui, o I nterface Segregação Princípio (ISP) eo L iskov Substituição Pricinple (LSP) tem algo a dizer.
O ISP nos diz para manter nossas interfaces o menor possível. Mas, ao herdar de ArrayList, você obtém muitos, muitos métodos. É significativo para get(), remove(), set()(substituir), ou add()(inserir) um nó filho em um índice específico? É sensato à ensureCapacity()lista subjacente? O que isso significa para sort()um nó? Os usuários da sua classe realmente deveriam receber um subList()? Como você não pode ocultar os métodos que não deseja, a única solução é ter o ArrayList como uma variável de membro e encaminhar todos os métodos que você realmente deseja:
private final ArrayList<Node> children = new ArrayList();
public void add(Node child) { children.add(child); }
public Iterator<Node> iterator() { return children.iterator(); }
Se você realmente deseja todos os métodos que vê na documentação, podemos seguir para o LSP. O LSP nos diz que devemos poder usar a subclasse onde quer que a classe pai seja esperada. Se uma função usa um ArrayListparâmetro as e passamos o nosso Node, nada deve mudar.
A compatibilidade das subclasses começa com coisas simples, como assinaturas de tipo. Quando você substitui um método, não pode tornar os tipos de parâmetro mais rigorosos, pois isso pode excluir usos legais da classe pai. Mas isso é algo que o compilador verifica para nós em Java.
Mas o LSP é muito mais profundo: precisamos manter a compatibilidade com tudo o que é prometido pela documentação de todas as classes e interfaces-pai. Em sua resposta , Lynn encontrou um desses casos em que a Listinterface (que você herdou por ArrayList) garante como os métodos equals()e hashCode()devem funcionar. Pois hashCode()você recebe um algoritmo específico que deve ser implementado exatamente. Vamos supor que você tenha escrito isso 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);
}
...
}
Isso requer que o valuenão possa contribuir para hashCode()e não possa influenciar equals(). A Listinterface - que você promete honrar herdando dela - precisa new Node(0).equals(new Node(123))ser verdadeira.
Como a herança de classes facilita demais a quebra acidental de uma promessa feita por uma classe pai e, como geralmente expõe mais métodos do que você pretendia, geralmente é sugerido que você prefira composição do que herança . Se você deve herdar algo, é recomendável herdar apenas interfaces. Se você deseja reutilizar o comportamento de uma classe específica, pode mantê-lo como um objeto separado em uma variável de instância, para que todas as suas promessas e requisitos não sejam impostos a você.
Às vezes, nossa linguagem natural sugere uma relação de herança: um carro é um veículo. Uma motocicleta é um veículo. Devo definir classes Care Motorcycleque herdam de uma Vehicleclasse? O design orientado a objetos não se trata de espelhar o mundo real exatamente em nosso código. Não podemos codificar facilmente as ricas taxonomias do mundo real em nosso código fonte.
Um exemplo é o problema de modelagem empregado-chefe. Temos vários Persons, cada um com um nome e endereço. Um Employeeé um Persone tem um Boss. A Bosstambém é a Person. Então, devo criar uma Personclasse que é herdada por Bosse Employee? Agora eu tenho um problema: o chefe também é funcionário e tem outro superior. Então parece que Bossdeveria se estender Employee. Mas o CompanyOwneré um Bossmas não é um Employee? De qualquer forma, qualquer tipo de gráfico de herança será dividido aqui.
OOP não é sobre hierarquias, herança e reutilização de classes existentes, é sobre generalizar o comportamento . OOP é sobre "Eu tenho vários objetos e quero que eles façam uma coisa em particular - e não me importo como." É para isso que servem as interfaces . Se você implementar a Iterableinterface para o seu computador Nodeporque deseja torná-lo iterável, tudo bem. Se você implementar a Collectioninterface porque deseja adicionar / remover nós filhos, etc., tudo bem. Mas herdar de outra classe, porque isso lhe dá tudo o que não é, ou pelo menos não, a menos que você tenha pensado cuidadosamente, conforme descrito acima.