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 ArrayList
parâ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 List
interface (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 value
não possa contribuir para hashCode()
e não possa influenciar equals()
. A List
interface - 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 Car
e Motorcycle
que herdam de uma Vehicle
classe? 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 Person
s, cada um com um nome e endereço. Um Employee
é um Person
e tem um Boss
. A Boss
também é a Person
. Então, devo criar uma Person
classe que é herdada por Boss
e Employee
? Agora eu tenho um problema: o chefe também é funcionário e tem outro superior. Então parece que Boss
deveria se estender Employee
. Mas o CompanyOwner
é um Boss
mas 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 Iterable
interface para o seu computador Node
porque deseja torná-lo iterável, tudo bem. Se você implementar a Collection
interface 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.