Por que evitar a herança de Java “estende”

40

Jame Gosling disse

"Você deve evitar a herança da implementação sempre que possível."

e, em vez disso, use herança de interface.

Mas por que? Como podemos evitar herdar a estrutura de um objeto usando a palavra-chave "extends" e, ao mesmo tempo, tornar nosso código Orientado a Objetos?

Alguém poderia dar um exemplo orientado a objetos que ilustra esse conceito em um cenário como "encomendar um livro em uma livraria?"

novato
fonte
O problema é que você só pode estender uma classe.

Respostas:

38

Gosling não disse para evitar o uso da palavra-chave extends ... ele apenas disse para não usar herança como um meio de conseguir reutilizar o código.

A herança não é ruim por si só e é uma ferramenta (essencial) muito poderosa para usar na criação de estruturas OO. No entanto, quando não é usado corretamente (ou seja, quando usado para outra coisa que não a criação de estruturas de Objeto), ele cria um código que é muito acoplado e muito difícil de manter.

Como a herança deve ser usada para criar estruturas polimórficas, isso pode ser feito com a mesma facilidade com interfaces, portanto, a instrução para usar interfaces em vez de classes ao projetar suas estruturas de objetos. Se você se encontra usando extends como forma de evitar o código de copiar e colar de um objeto para outro, talvez esteja usando errado e seria melhor atendido com uma classe especializada para lidar com essa funcionalidade e compartilhar esse código por meio da composição.

Leia sobre o princípio da substituição de Liskov e entenda. Sempre que você estiver prestes a estender uma classe (ou uma interface), pergunte a si mesmo se você está violando o princípio; se sim, é mais provável que (a) use herança de maneiras não naturais. É fácil de fazer e geralmente parece a coisa certa, mas tornará seu código mais difícil de manter, testar e evoluir.

--- Editar Esta resposta é um bom argumento para responder à pergunta em termos mais gerais.

Newtopian
fonte
3
A herança (implementação) não é essencial para a criação de estruturas OO. Você pode ter um idioma OO sem ele.
Andres F.
Você poderia dar um exemplo? Eu nunca vi OO sem herança.
Newtopian
11
O problema é que não há uma definição aceita do que é OOP. (Além disso, até Alan Kay lamentou a interpretação comum de sua definição, com sua ênfase atual em "objetos" em vez de "mensagens"). Aqui está uma tentativa de resposta para sua pergunta . Concordo que é incomum ver OOP sem herança; daqui meu argumento era meramente que não é essencial ;)
Andres F.
9

Nos primeiros dias da programação orientada a objetos, a herança de implementação era o martelo de ouro da reutilização de código. Se havia alguma funcionalidade em alguma classe que você precisava, a coisa a fazer era herdar publicamente dessa classe (esses eram os dias em que o C ++ era mais prevalente que o Java) e você obtinha essa funcionalidade "de graça".

Exceto que não era realmente gratuito. O resultado foram hierarquias de herança extremamente complicadas que eram quase impossíveis de entender, incluindo árvores de herança em forma de diamante que eram difíceis de manusear. Isso é parte do motivo pelo qual os designers de Java optaram por não suportar herança múltipla de classes.

O conselho de Gosling (e outras declarações) como se fosse um remédio forte para uma doença bastante séria, e é possível que alguns bebês sejam jogados fora com a água do banho. Não há nada de errado em usar a herança para modelar um relacionamento entre classes que realmente é is-a. Mas se você quiser usar uma funcionalidade de outra classe, será melhor investigar outras opções primeiro.

JohnMcG
fonte
6

A herança reuniu bastante reputação assustadora recentemente. Um motivo para evitá-lo é o princípio do design de "composição sobre herança", que basicamente encoraja as pessoas a não usarem herança onde esse não é o verdadeiro relacionamento, apenas para economizar algum código. Esse tipo de herança pode causar alguns erros difíceis de encontrar e difíceis de corrigir. A razão pela qual seu amigo provavelmente estava sugerindo o uso de uma interface é porque as interfaces fornecem uma solução mais flexível e sustentável de APIs distribuídas. Com mais freqüência, eles permitem que alterações sejam feitas em uma API sem quebrar o código do usuário, onde a exposição de classes geralmente quebra o código do usuário. O melhor exemplo disso em .Net é a diferença de uso entre List e IList.

Morgan Herlocker
fonte
5

Porque você só pode estender uma classe, enquanto pode implementar muitas interfaces.

Uma vez ouvi dizer que a herança define o que você é, mas as interfaces definem um papel que você pode desempenhar.

As interfaces também permitem um melhor uso do polimorfismo.

Isso pode ser parte do motivo.

Mahmoud Hossam
fonte
por que o voto negativo?
Mahmoud Hossam
6
A resposta coloca a carroça diante do cavalo. O Java apenas permite que você estenda uma classe por causa do princípio articulado por Gosling (designer de Java). É como dizer que as crianças devem usar capacete enquanto andam de bicicleta, porque essa é a lei. Isso é verdade, mas a lei e os conselhos decorrem da prevenção de ferimentos na cabeça.
31711 JohnMcG
@JohnMcG Não estou explicando a escolha do design que Gosling fez, estou apenas dando conselhos a um codificador, os codificadores geralmente não se preocupam com o design da linguagem.
Mahmoud Hossam
1

Também me ensinaram isso e prefiro interfaces sempre que possível (é claro que ainda uso herança onde faz sentido).

Uma coisa que eu acho que faz é separar seu código de implementações específicas. Digamos que eu tenha uma classe chamada ConsoleWriter e que seja passada para um método para escrever algo e que seja gravado no console. Agora, digamos que eu queira mudar para a impressão em uma janela da GUI. Bem, agora eu tenho que modificar o método ou escrever um novo que use o GUIWriter como parâmetro. Se eu iniciasse definindo uma interface IWriter e tivesse o método em um IWriter, poderia começar com o ConsoleWriter (que implementaria a interface IWriter) e depois escrever uma nova classe chamada GUIWriter (que também implementa a interface IWriter) e então eu teria apenas que mudar a classe que estava sendo aprovada.

Outra coisa (o que é verdade para C #, não tenho certeza sobre Java) é que você só pode estender 1 classe, mas implementar muitas interfaces. Digamos que eu tive aulas denominadas Teacher, MathTeacher e HistoryTeacher. Agora MathTeacher e HistoryTeacher se estendem do Teacher, mas e se quisermos uma classe que represente alguém que seja um MathTeacher e HistoryTeacher. Pode ficar bastante confuso ao tentar herdar de várias classes quando você pode fazê-lo apenas uma de cada vez (existem maneiras, mas elas não são exatamente ideais). Com interfaces, você pode ter 2 interfaces chamadas IMathTeacher e IHistoryTeacher e, em seguida, ter uma classe que se estende do Teacher e implementa essas 2 interfaces.

Uma desvantagem do uso de interfaces é que, às vezes, vejo pessoas duplicar código (já que você precisa criar a implementação para cada classe, implementar a interface), no entanto, existe uma solução limpa para esse problema, por exemplo, o uso de coisas como delegados (não tenho certeza qual o Java equivalente a isso).

O principal motivo para usar interfaces sobre heranças é a dissociação do código de implementação, mas não pense que a herança seja ruim, pois ainda é muito útil.

ryanzec
fonte
2
"você só pode estender 1 classe, mas implementar muitas interfaces" Isso é verdade tanto para Java quanto para C #.
FrustratedWithFormsDesigner
Uma solução possível para o problema da duplicação de código é criar uma classe abstrata que implemente a interface e a estenda. Use com cuidado, no entanto; vi apenas alguns casos que fazem sentido.
Michael K
0

Se você herdar de uma classe, terá uma dependência não apenas dessa classe, mas também precisará honrar quaisquer contratos implícitos que tenham sido criados pelos clientes dessa classe. O tio Bob fornece um ótimo exemplo de como é fácil violar sutilmente o Princípio de Substituição de Liskov usando o dilema básico de Retângulo estendido do quadrado . As interfaces, uma vez que não têm implementação, também não têm contratos ocultos.

Michael Brown
fonte
2
Uma dica: o problema da elipse circular ainda ocorrerá mesmo que apenas interfaces puras sejam usadas, se os objetos forem mutáveis.
rwong 11/05