JavaRush /Blogue Java /Random-PT /Herança como fenômeno
articles
Nível 15

Herança como fenômeno

Publicado no grupo Random-PT
Para falar a verdade, inicialmente não planejei este artigo. Considerei as questões que quero discutir aqui triviais, nem dignas de menção. Porém, no processo de escrever artigos para este site, levantei uma discussão sobre herança múltipla em um dos fóruns. Como resultado, descobriu-se que a maioria dos desenvolvedores tem uma compreensão muito vaga de herança. E, conseqüentemente, ele comete muitos erros. Como a herança é uma das características mais importantes da OOP (se não a mais importante!), decidi dedicar um artigo separado a esse fenômeno. * * * Primeiro, quero distinguir entre dois conceitos - objeto e classe. Esses conceitos são constantemente confundidos. Enquanto isso, eles são fundamentais para OOP. E, na minha opinião, é preciso conhecer as diferenças entre eles. Então, o objeto. Basicamente, é qualquer coisa. Aqui está o cubo. De madeira, azul. O comprimento da borda é de 5 cm, este é um objeto. E há uma pirâmide. Plástico, vermelho. Costela de 10 cm. Este também é um objeto. O que eles têm em comum? Tamanhos diferentes. Forma diferente. Matéria diferente. No entanto, eles têm algo em comum. Em primeiro lugar, tanto o cubo como a pirâmide são poliedros regulares. Aqueles. a soma do número de vértices e do número de faces é 2 a mais que o número de arestas. Avançar. Ambas as formas possuem faces, arestas e vértices. Ambas as figuras têm uma característica como o tamanho das costelas. Ambas as formas podem ser giradas. Ambas as figuras podem ser desenhadas. As duas últimas propriedades já são comportamento. E assim por diante. A prática de programação mostra que é muito mais fácil operar com objetos homogêneos do que com objetos heterogêneos. E como ainda há algo em comum entre essas figuras, há um desejo de destacar de alguma forma esse ponto em comum. É aqui que o conceito de classe entra em jogo. Então, a definição.
Uma classe é um descritor das propriedades comuns de um grupo de objetos. Essas propriedades podem ser características de objetos (tamanho, peso, cor, etc.) e comportamentos, funções, etc.
Comente. A palavra “todos” (descritor de todas as propriedades) não foi pronunciada. O que significa que qualquer objeto pode pertencer a várias classes diferentes. Herança como fenômeno - 1 Tomemos o mesmo exemplo com formas geométricas como base. A descrição mais geral é poliedro regular . Independentemente do tamanho da aresta, número de faces e vértices. A única coisa que sabemos é que esta figura tem vértices, arestas e faces, e que os comprimentos das arestas são iguais. Avançar. Podemos tornar a descrição mais específica. Digamos que queremos desenhar este poliedro . Vamos apresentar um conceito como poliedro regular desenhado . O que precisamos para desenhar? Descrição de um método geral de desenho que não depende de coordenadas de vértices específicas. Talvez a cor do objeto. Agora vamos apresentar as classes Cubo e Tetraedro . Os objetos pertencentes a essas classes são certamente poliedros regulares. A única diferença é que os números de vértices, arestas e faces já estão estritamente fixos para cada uma das novas classes. Além disso, conhecendo o tipo de uma figura específica, podemos dar uma descrição do método de desenho. Isso significa que qualquer objeto das classes Cubo ou Tetraedro também é objeto da classe Poliedro regular desenhado . Existe uma hierarquia de classes. Nesta hierarquia descemos da descrição mais geral para a mais específica. Observe que um objeto de qualquer classe também se ajusta à descrição de qualquer classe mais geral na hierarquia. Esse relacionamento de classe é chamado de herança . Cada classe filha herda todas as propriedades da classe pai, mais gerais, e (possivelmente) adiciona algumas propriedades próprias a essas propriedades. Ou substitui algumas propriedades da classe pai. Aqui quero citar o livro clássico de Gradi Bucha sobre design orientado a objetos:
A herança, portanto, define uma hierarquia "é uma" entre classes, na qual a subclasse herda de uma ou mais superclasses. Este é, na verdade, o teste decisivo para a herança. Dadas as classes A e B, se A “não é” um tipo de B, então A não deveria ser uma subclasse de B.
Traduzido, parece assim:
A herança define, portanto, uma hierarquia "é" entre classes, na qual uma subclasse herda de uma ou mais superclasses. Este é, na verdade, o teste definidor (literalmente, um teste decisivo, nota minha) para herança. Se tivermos classes A e B, e se a classe A “não for” uma variante da classe B, então A não deve ser uma subclasse de B.
Aqueles que leram até aqui provavelmente girarão o dedo na têmpora, perplexos. O primeiro pensamento é que isso é trivial! Isto é verdade. Mas se você soubesse quantas hierarquias de herança malucas eu já vi! Naquela discussão que mencionei logo no início, um dos participantes herdou seriamente um tanque de... uma metralhadora!!! Pela simples razão de que o tanque TEM uma metralhadora. E este é o erro mais comum. Herança é confundida com agregação – a inclusão de um objeto dentro de outro. O tanque não é uma metralhadora, ele contém uma. E por causa desse erro, na maioria das vezes surge o desejo de usar herança múltipla. Vamos agora passar diretamente para Java. O que há em termos de herança? Existem dois tipos de classes na linguagem – aquelas capazes de conter uma implementação e aquelas incapazes de fazê-lo. Estas últimas são chamadas de interfaces, embora em essência sejam classes completamente abstratas. Herança como fenômeno - 2 Portanto, a linguagem permite herdar uma classe de outra classe que potencialmente contém uma implementação. MAS SÓ DE UM! Deixe-me explicar por que isso foi feito. A questão é que cada implementação só pode lidar com sua parte - com as variáveis ​​​​e métodos que conhece. E mesmo que herdamos a classe C de A e B , então o método processA , herdado da classe A, só pode funcionar com a variável interna a , porque não sabe nada sobre b , assim como não sabe nada sobre c e o método processC . Da mesma forma, o método processB só pode trabalhar com a variável b. Isto é, em essência, as partes herdadas são isoladas. A classe C certamente pode funcionar com eles, mas também pode funcionar com essas partes se elas forem simplesmente incluídas como parte dela, em vez de herdadas. Porém, há outro incômodo aqui, que é a sobreposição de nomes. Se os métodos processA e processB tivessem o mesmo nome, digamos process, então que efeito teria a chamada do método process da classe C ? Qual dos dois métodos seria chamado? É claro que C++ possui controles nessa situação, mas isso não acrescenta harmonia à linguagem. Portanto, a herança de implementação não oferece vantagens, mas existem desvantagens. Por esta razão, esta herança de implementação em Java foi abandonada. No entanto, os desenvolvedores ficaram com essa opção de herança múltipla como herança de uma interface. Em termos Java, uma implementação de interface. Qual é a interface? Um conjunto de métodos. (Atualmente não estamos considerando a definição de constantes em interfaces; mais sobre isso aqui .) O que é um método? E um método, em sua essência, determina o comportamento de um objeto. Não é por acaso que o nome de quase todos os métodos contém uma ação - getXXX , drawXXX , countXXX , etc. E como uma interface é uma coleção de métodos, a interface é, na verdade, um determinante do comportamento . Outra opção para usar uma interface é definir a função de um objeto. Observador, Ouvinte , etc. Neste caso, o método é na verdade a concretização de uma reação a algum evento externo. Isso é, novamente, comportamento. É claro que um objeto pode ter vários comportamentos diferentes. Se precisar ser renderizado, ele será renderizado. Se ele precisa ser salvo, ele está salvo. Bem, etc. Conseqüentemente, a capacidade de herdar classes que definem o comportamento é muito, muito útil. Da mesma forma, um objeto pode ter diversas funções diferentes. No entanto, a implementaçãoo comportamento depende inteiramente da consciência da classe infantil. A herança de uma interface (sua implementação) diz que um objeto desta classe deve ser capaz de fazer isso e aquilo. E COMO isso acontece é determinado independentemente por cada classe que implementa a interface. Voltemos aos erros de herança. Minha experiência no desenvolvimento de vários sistemas mostra que tendo herança de interfaces, você pode implementar qualquer sistema sem usar herança de múltiplas implementações. E, portanto, quando encontro reclamações sobre a falta de herança múltipla na forma em que existe em C++, para mim isso é um sinal claro de design incorreto. O erro mais comum cometido é o que já mencionei - herança é confundida com agregação. Às vezes isso acontece devido a suposições incorretas. Aqueles. Tomemos, por exemplo, um velocímetro, argumenta-se que a velocidade só pode ser medida medindo a distância e o tempo, após o que o velocímetro é herdado com sucesso da régua e do relógio, tornando-se assim a régua e o relógio, de acordo com a definição de herança. (Meus pedidos para medir o tempo com um velocímetro geralmente eram respondidos com piadas. Ou nem respondiam.) Qual é o erro aqui? Na premissa. O fato é que o velocímetro não mede o tempo. E as distâncias, aliás, também. O hodômetro, que se encontra em qualquer velocímetro, é um exemplo clássico de segundo dispositivo na mesma caixa, ou seja, agregação. Não é necessário medir a velocidade. Ele pode ser totalmente removido - isso não afetará de forma alguma a medição da velocidade. Às vezes, esses erros são cometidos deliberadamente. Isto é muito pior. “Sim, eu sei que é errado, mas é mais conveniente para mim.” A que isso poderia levar? Mas eis o seguinte: herdaremos um tanque de um canhão e de uma metralhadora. É mais conveniente assim. Como resultado, o tanque se torna um canhão e uma metralhadora. A seguir equiparemos o avião com duas metralhadoras e um canhão. O que ganhamos? Uma aeronave com armas suspensas em forma de três tanques! Porque DEFINITIVAMENTE existe uma pessoa que, sem entender, usa um tanque como metralhadora. Exclusivamente de acordo com a hierarquia de herança. E ele terá toda razão, porque quem desenhou tal hierarquia cometeu um erro.
Em geral, eu realmente não entendo a abordagem “ é mais conveniente para mim assim”. É conveniente escrever como um ouvinte, e aqueles que dizem gramática básica são estúpidos. Estou exagerando, claro, mas a ideia principal permanece - além da comodidade momentânea, existe a alfabetização. Este conceito é definido com base na experiência de um grande número de pessoas. Na verdade, isso é o que em inglês se chama “melhores práticas” – a melhor solução. E na maioria das vezes, soluções que parecem mais simples trazem muitos problemas no futuro.
Este exemplo, é claro, é muito exagerado e, portanto, absurdo. No entanto, existem casos menos óbvios que, no entanto, levam a consequências catastróficas. Ao herdar de um objeto, em vez de agregá-lo, o desenvolvedor dá a qualquer pessoa a capacidade de usar diretamente a funcionalidade do objeto pai. Com tudo o que isso implica. Imagine que você tem uma classe que trabalha com banco de dados, DBManager . Você cria outra classe que irá trabalhar com seus dados usando DBManager - DataManager . Esta classe realizará controle de dados, transformações, ações adicionais, etc. Em geral, uma camada entre a camada de negócios e a camada base. Se você herdar o DataManager do DBManager, qualquer pessoa que o utilize terá acesso direto ao banco de dados. E, portanto, ele poderá realizar quaisquer ações contornando controles, transformações, etc. Ok, vamos supor que ninguém queira causar danos intencionais e as ações diretas serão competentes. Mas! Vamos supor que a base mudou. Quer dizer, alguns princípios de controle ou transformação mudaram. O DataManager foi alterado. Mas o código que anteriormente funcionava diretamente com o banco de dados continuará funcionando. Provavelmente não se lembrarão dele. O resultado será um erro de tal classe que quem o procurar ficará cinza. Nunca ocorreria a ninguém que eles trabalham com o banco de dados ignorando o DataManager. Aliás, um exemplo da vida real. Demorou MUITO para encontrar o erro. Finalmente, direi novamente. A herança SÓ deve ser usada quando houver um relacionamento "é". Porque esta é a essência da herança - a capacidade de usar objetos de uma classe filha como objetos de uma classe base. Se não houver relacionamento “é” entre as classes, NÃO DEVE haver herança!!! Nunca e sob nenhuma circunstância. E ainda mais – só porque é muito conveniente. Link para a fonte original: http://www.skipy.ru/philosophy/inheritance.html
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION