Herança (programação orientada a objetos)

Da Wikipédia, a enciclopédia livre
Ir para navegação Pular para pesquisar

Na programação orientada a objetos , a herança é o mecanismo de basear um objeto ou classe em outro objeto ( herança baseada em protótipo ) ou classe ( herança baseada em classe ), mantendo uma implementação semelhante . Também definido como derivar novas classes ( subclasses ) das existentes, como superclasse ou classe base e, em seguida, formá-las em uma hierarquia de classes. Na maioria das linguagens orientadas a objetos baseadas em classes, um objeto criado por herança, um "objeto filho", adquire todas as propriedades e comportamentos do "objeto pai", com exceção de: construtores , destruidor,operadores sobrecarregados e funções amigas da classe base. A herança permite que os programadores criem classes que são construídas sobre as classes existentes, [1] para especificar uma nova implementação enquanto mantém os mesmos comportamentos ( realizando uma interface ), para reutilizar o código e estender independentemente o software original por meio de classes públicas e interfaces . Os relacionamentos de objetos ou classes por meio de herança dão origem a um grafo direcionado .

A herança foi inventada em 1969 para Simula [2] e agora é usada em muitas linguagens de programação orientadas a objetos, como Java , C ++ e Python .

Uma classe herdada é chamada de subclasse de sua classe pai ou superclasse. O termo "herança" é vagamente usado para programação baseada em classe e baseada em protótipo, mas em uso restrito o termo é reservado para programação baseada em classe (uma classe herda de outra), com a técnica correspondente na programação baseada em protótipo sendo em vez disso, chamado de delegação (um objeto delega para outro).

Herança não deve ser confundida com subtipagem . [3] [4] Em alguns idiomas, a herança e a subtipagem concordam, [a] enquanto em outros eles diferem; em geral, a subtipagem estabelece uma relação é-um , enquanto a herança apenas reutiliza a implementação e estabelece uma relação sintática, não necessariamente uma relação semântica (a herança não garante a subtipagem comportamental). Para distinguir esses conceitos, a subtipagem também é conhecida como herança de interface , enquanto a herança, conforme definida aqui, é conhecida como herança de implementação ou herança de código . [5]Ainda assim, a herança é um mecanismo comumente usado para estabelecer relacionamentos de subtipos. [6]

Herança é contrastada com composição de objeto , onde um objeto contém outro objeto (ou objetos de uma classe contêm objetos de outra classe); veja composição sobre herança . A composição implementa um relacionamento tem-um , em contraste com o relacionamento é-um de subtipagem.

Tipos

Herança única
Herança múltipla

Existem vários tipos de herança, com base em paradigma e linguagem específica. [7]

Herança única

onde as subclasses herdam os recursos de uma superclasse. Uma classe adquire as propriedades de outra classe.

Herança múltipla

onde uma classe pode ter mais de uma superclasse e herdar recursos de todas as classes pai.

"A herança múltipla  ... era amplamente considerada muito difícil de implementar com eficiência. Por exemplo, em um resumo de C ++ em seu livro sobre Objective C , Brad Cox na verdade afirmou que adicionar herança múltipla ao C ++ era impossível. Portanto, a herança múltipla parecia mais um desafio. Como eu havia considerado a herança múltipla já em 1982 e encontrado uma técnica de implementação simples e eficiente em 1984, não pude resistir ao desafio. Suspeito que este seja o único caso em que a moda afetou a sequência de eventos . " [8]

Herança multinível

onde uma subclasse é herdada de outra subclasse. Não é incomum que uma classe seja derivada de outra classe derivada, conforme mostrado na figura "Herança multinível".

Herança multinível

A classe A serve como uma classe base para a classe de derivados B , que por sua vez serve como uma classe base para a classe derivada C . A classe B é conhecida como intermediário classe base, porque fornece uma ligação para a herança entre A e C . A cadeia ABC é conhecida como caminho de herança .

Uma classe derivada com herança multinível é declarada da seguinte maneira:

Classe  A (...);       // Classe base 
Classe  B  :  public  A (...);    // B derivado de A 
Classe  C  :  public  B (...);    // C derivado de B

Este processo pode ser estendido a qualquer número de níveis.

Herança hierárquica

É aqui que uma classe serve como superclasse (classe base) para mais de uma subclasse. Por exemplo, uma classe pai, A, pode ter duas subclasses B e C. As classes pai de B e C são A, mas B e C são duas subclasses separadas.

Herança híbrida

Herança híbrida é quando ocorre uma combinação de dois ou mais dos tipos de herança acima. Um exemplo disso é quando a classe A tem uma subclasse B que possui duas subclasses, C e D. Esta é uma mistura de herança multinível e herança hierárquica.

Subclasses e superclasses

Subclasses , as classes derivadas , aulas de herdeiro , ou classes filhas são modulares aulas derivativos que herda um ou mais língua entidades de uma ou mais outras classes (chamados superclasse , classes base ou classes pai ). A semântica da herança de classes varia de idioma para idioma, mas normalmente a subclasse herda automaticamente as variáveis ​​de instância e funções de membro de suas superclasses.

A forma geral de definir uma classe derivada é: [9]

classe  Subclasse :  visibilidade  SuperClasse 
{ 
    // membros da subclasse 
};
  • Os dois pontos indicam que a subclasse herda da superclasse. A visibilidade é opcional e, se houver, pode ser privada ou pública . A visibilidade padrão é privada . Visibilidade especifica se os recursos da classe base são derivados de forma privada ou derivada publicamente .

Algumas linguagens também suportam a herança de outras construções. Por exemplo, em Eiffel , os contratos que definem a especificação de uma classe também são herdados pelos herdeiros. A superclasse estabelece uma interface comum e funcionalidade básica, que subclasses especializadas podem herdar, modificar e complementar. O software herdado por uma subclasse é considerado reutilizado na subclasse. Uma referência a uma instância de uma classe pode, na verdade, estar se referindo a uma de suas subclasses. A classe real do objeto que está sendo referenciado é impossível de prever em tempo de compilação. Uma interface uniforme é usada para invocar as funções-membro de objetos de várias classes diferentes. As subclasses podem substituir funções de superclasse por funções inteiramente novas que devem compartilhar a mesma assinatura de método .

Classes não-sub-classe

Em algumas linguagens, uma classe pode ser declarada como não subclassível adicionando certos modificadores de classe à declaração da classe. Os exemplos incluem a finalpalavra - chave em Java e C ++ 11 em diante ou a sealedpalavra - chave em C #. Esses modificadores são adicionados à declaração da classe antes da classpalavra - chave e da declaração do identificador da classe. Essas classes não subclassíveis restringem a reutilização , particularmente quando os desenvolvedores só têm acesso a binários pré-compilados e não ao código-fonte .

Uma classe não subclassível não tem subclasses, então pode ser facilmente deduzido em tempo de compilação que referências ou ponteiros para objetos dessa classe estão na verdade referenciando instâncias dessa classe e não instâncias de subclasses (elas não existem) ou instâncias de superclasses ( atualizar um tipo de referência viola o sistema de tipos). Como o tipo exato do objeto referenciado é conhecido antes da execução, a vinculação inicial (também chamada de despacho estático ) pode ser usada em vez da vinculação tardia (também chamada de despacho dinâmico ), que requer uma ou mais pesquisas de tabela de método virtual, dependendo de herança múltipla ou apenasherança única é suportada na linguagem de programação que está sendo usada.

Métodos não-substituíveis

Assim como as classes podem não ser subclassíveis, as declarações de método podem conter modificadores de método que evitam que o método seja sobrescrito (isto é, substituído por uma nova função com o mesmo nome e assinatura de tipo em uma subclasse). Um método privado não pode ser substituído simplesmente porque não é acessível por classes diferentes da classe da qual ele é uma função de membro (embora isso não seja verdade para C ++). Um finalmétodo em Java, um sealedmétodo em C # ou um frozenrecurso em Eiffel não pode ser substituído.

Métodos virtuais

Se o método da superclasse for um método virtual , as invocações do método da superclasse serão despachadas dinamicamente . Algumas linguagens exigem que os métodos sejam especificamente declarados como virtuais (por exemplo, C ++) e, em outras, todos os métodos são virtuais (por exemplo, Java). Uma invocação de um método não virtual sempre será despachada estaticamente (isto é, o endereço da chamada de função é determinado em tempo de compilação). O despacho estático é mais rápido do que o despacho dinâmico e permite otimizações como a expansão em linha .

Visibilidade dos membros herdados

A tabela a seguir mostra quais variáveis ​​e funções são herdadas dependendo da visibilidade fornecida ao derivar a classe. [10]

Visibilidade da classe base Visibilidade de classe derivada
Derivação pública Derivação privada Derivação protegida
  • Privado →
  • Protegido →
  • Público →
  • Não herdado
  • Protegido
  • Público
  • Não herdado
  • Privado
  • Privado
  • Não herdado
  • Protegido
  • Protegido

Aplicações

A herança é usada para co-relacionar duas ou mais classes entre si.

Substituindo

Ilustração de substituição de método

Muitas linguagens de programação orientadas a objetos permitem que uma classe ou objeto substitua a implementação de um aspecto - normalmente um comportamento - que ele herdou. Este processo é chamado de substituição. A substituição introduz uma complicação: qual versão do comportamento uma instância da classe herdada usa - aquela que faz parte de sua própria classe ou aquela da classe pai (base)? A resposta varia entre as linguagens de programação e algumas linguagens fornecem a capacidade de indicar que um determinado comportamento não deve ser substituído e deve se comportar conforme definido pela classe base. Por exemplo, em C #, o método ou propriedade base só pode ser substituído em uma subclasse se estiver marcado com o modificador virtual, abstrato ou de substituição, enquanto em linguagens de programação como Java, diferentes métodos podem ser chamados para substituir outros métodos. [11] Uma alternativa para substituir é ocultar o código herdado.

Reutilização de código

Herança de implementação é o mecanismo pelo qual uma subclasse reutiliza o código em uma classe base. Por padrão, a subclasse retém todas as operações da classe base, mas a subclasse pode sobrescrever algumas ou todas as operações, substituindo a implementação da classe base pela sua própria.

No exemplo Python a seguir, as subclasses SquareSumComputer e CubeSumComputer substituem o método transform () da classe base SumComputer . A classe base compreende operações para calcular a soma dos quadrados entre dois inteiros. A subclasse reutiliza todas as funcionalidades da classe base, com exceção da operação que transforma um número em seu quadrado, substituindo-o por uma operação que transforma um número em seu quadrado e cubo, respectivamente. As subclasses, portanto, calculam a soma dos quadrados / cubos entre dois inteiros.

Abaixo está um exemplo de Python.

class  SumComputer : 
    def  __init__ ( self ,  a ,  b ): 
        self . a  =  um 
        self . b  =  b

    def  transform ( self ,  x ): 
        raise  NotImplementedError

     entradas def ( self ): 
        faixa de retorno  ( self . a , self . b ) 

    def  compute ( self ): 
        retorna a  soma ( self . transform ( value )  para o  valor  em  self . entradas ())

class  SquareSumComputer ( SumComputer ): 
    def  transform ( self ,  x ): 
        return  x  *  x

classe  CubeSumComputer ( SumComputer ): 
    def  transform ( self ,  x ): 
        return  x  *  x  *  x

Na maioria dos trimestres, a herança de classes com o único propósito de reutilização de código caiu em desuso. [ carece de fontes? ] A principal preocupação é que a herança de implementação não fornece qualquer garantia de substituibilidade polimórfica - uma instância da classe de reutilização não pode necessariamente ser substituída por uma instância da classe herdada. Uma técnica alternativa, delegação explícita , requer mais esforço de programação, mas evita o problema de substituibilidade. [ carece de fontes? ] Em C ++, a herança privada pode ser usada como uma forma de herança de implementaçãosem substituibilidade. Enquanto a herança pública representa um relacionamento "é um" e a delegação representa um relacionamento "tem um", a herança privada (e protegida) pode ser considerada como um relacionamento "é implementado em termos de". [12]

Outro uso frequente de herança é garantir que as classes mantenham uma certa interface comum; ou seja, eles implementam os mesmos métodos. A classe pai pode ser uma combinação de operações implementadas e operações que devem ser implementadas nas classes filhas. Freqüentemente, não há mudança de interface entre o supertipo e o subtipo - o filho implementa o comportamento descrito em vez de sua classe pai. [13]

Herança vs subtipos

A herança é semelhante, mas distinta da subtipagem. [14] Subtipagem permite que um determinado tipo de ser substituído por outro tipo ou captação, e é dito para estabelecer um é-um relacionamento entre o subtipo e alguns captação existente, implícita ou explicitamente, dependendo do suporte de idioma. O relacionamento pode ser expresso explicitamente por meio de herança em linguagens que suportam herança como um mecanismo de subtipagem. Por exemplo, o código C ++ a seguir estabelece uma relação de herança explícita entre as classes B e A , onde B é uma subclasse e um subtipo de A , e pode ser usado como A sempre que um B é especificado (por meio de uma referência, um ponteiro ou o próprio objeto).

classe  A  { 
 public : 
  void  DoSomethingALike ()  const  {} 
};

classe  B  :  public  A  { 
 public : 
  void  DoSomethingBLike ()  const  {} 
};

void  UseAnA ( const  A &  a )  { 
  a . DoSomethingALike (); 
}

anula  SomeFunc ()  { 
  B  b ; 
  UseAnA ( b );   // b pode ser substituído por um A. 
}

Em linguagens de programação que não suportam herança como mecanismo de subtipagem , o relacionamento entre uma classe base e uma classe derivada é apenas um relacionamento entre implementações (um mecanismo para reutilização de código), em comparação com um relacionamento entre tipos . A herança, mesmo em linguagens de programação que suportam herança como um mecanismo de subtipagem, não envolve necessariamente a subtipagem comportamental . É inteiramente possível derivar uma classe cujo objeto se comportará incorretamente quando usado em um contexto onde a classe pai é esperada; veja o princípio de substituição de Liskov . [15] (Compare conotação / denotação.) Em algumas linguagens OOP, as noções de reutilização e subtipagem de código coincidem porque a única maneira de declarar um subtipo é definir uma nova classe que herda a implementação de outra.

Restrições de design

O uso extensivo da herança no projeto de um programa impõe certas restrições.

Por exemplo, considere uma classe Person que contém o nome de uma pessoa, data de nascimento, endereço e número de telefone. Podemos definir uma subclasse de Pessoa chamada Aluno, que contém a média de notas da pessoa e as aulas realizadas, e outra subclasse de Pessoa, chamada Funcionário, que contém o cargo, o empregador e o salário da pessoa.

Ao definir essa hierarquia de herança, já definimos certas restrições, nem todas desejáveis:

Solteiro
Usando herança única, uma subclasse pode herdar de apenas uma superclasse. Continuando o exemplo dado acima, Pessoa pode ser um Estudante ou um Funcionário , mas não ambos. O uso de herança múltipla resolve parcialmente esse problema, pois é possível definir uma classe StudentEmployee que herda tanto de Student quanto de Employee . No entanto, na maioria das implementações, ele ainda pode herdar de cada superclasse apenas uma vez e, portanto, não oferece suporte a casos em que um aluno tem dois empregos ou frequenta duas instituições. O modelo de herança disponível em Eiffel torna isso possível por meio do suporte para herança repetida .
Estático
A hierarquia de herança de um objeto é fixada na instanciação quando o tipo do objeto é selecionado e não muda com o tempo. Por exemplo, o gráfico de herança não permite que um objeto Student se torne um objeto Employee enquanto retém o estado de sua superclasse Person . (Esse tipo de comportamento, no entanto, pode ser obtido com o padrão decorator .) Alguns criticaram a herança, argumentando que ela prende os desenvolvedores aos seus padrões de design originais. [16]
Visibilidade
Sempre que o código do cliente tem acesso a um objeto, geralmente ele tem acesso a todos os dados da superclasse do objeto. Mesmo que a superclasse não tenha sido declarada pública, o cliente ainda pode converter o objeto para seu tipo de superclasse. Por exemplo, não há como fornecer a uma função um ponteiro para a média e a transcrição de notas de um Aluno sem também dar a essa função acesso a todos os dados pessoais armazenados na superclasse Person do aluno . Muitas linguagens modernas, incluindo C ++ e Java, fornecem um modificador de acesso "protegido" que permite que subclasses acessem os dados, sem permitir que qualquer código fora da cadeia de herança os acesse.

O princípio da reutilização composta é uma alternativa à herança. Esta técnica suporta polimorfismo e reutilização de código, separando comportamentos da hierarquia de classes primárias e incluindo classes de comportamento específicas conforme exigido em qualquer classe de domínio de negócios. Essa abordagem evita a natureza estática de uma hierarquia de classes, permitindo modificações de comportamento em tempo de execução e permite que uma classe implemente comportamentos no estilo buffet, em vez de ficar restrita aos comportamentos de suas classes ancestrais.

Questões e alternativas

A herança de implementação é controversa entre os programadores e teóricos da programação orientada a objetos desde pelo menos os anos 1990. Entre eles estão os autores de Design Patterns , que defendem a herança da interface e favorecem a composição em vez da herança. Por exemplo, o padrão decorator (como mencionado acima ) foi proposto para superar a natureza estática da herança entre as classes. Como uma solução mais fundamental para o mesmo problema, a programação orientada a papéis apresenta um relacionamento distinto, representado por , combinando propriedades de herança e composição em um novo conceito. [ citação necessária ]

De acordo com Allen Holub , o principal problema com a herança de implementação é que ela introduz acoplamento desnecessário na forma do "problema da classe base frágil" : [5] modificações na implementação da classe base podem causar mudanças comportamentais inadvertidas nas subclasses. O uso de interfaces evita esse problema porque nenhuma implementação é compartilhada, apenas a API. [16] Outra forma de afirmar isso é que "a herança quebra o encapsulamento ". [17] O problema surge claramente em sistemas orientados a objetos abertos, como frameworks, em que se espera que o código do cliente seja herdado de classes fornecidas pelo sistema e, em seguida, substituído pelas classes do sistema em seus algoritmos. [5]

Alegadamente, o inventor de Java James Gosling falou contra a herança de implementação, afirmando que ele não a incluiria se redesenhasse o Java. [16] Projetos de linguagem que desacoplam herança de subtipagem (herança de interface) apareceram já em 1990; [18] um exemplo moderno disso é a linguagem de programação Go .

Herança complexa, ou herança usada em um design insuficientemente maduro, pode levar ao problema de ioiô . Quando a herança foi usada como uma abordagem primária para estruturar o código em um sistema no final da década de 1990, os desenvolvedores naturalmente começaram a dividir o código em várias camadas de herança conforme a funcionalidade do sistema crescia. Se uma equipe de desenvolvimento combinasse várias camadas de herança com o princípio de responsabilidade única, ela criaria muitas camadas superfinas de código, muitas das quais teriam apenas 1 ou 2 linhas de código em cada camada. Muitas camadas tornam a depuração um desafio significativo, pois se torna difícil determinar qual camada precisa ser depurada.

Outro problema com a herança é que as subclasses devem ser definidas no código, o que significa que os usuários do programa não podem adicionar novas subclasses em tempo de execução. Outros padrões de design (como Entidade – componente – sistema ) permitem que os usuários do programa definam variações de uma entidade em tempo de execução.

Veja também

Notas

  1. ^ Isso geralmente é verdadeiro apenas em linguagens OO baseadas em classe com tipagem estática, como C ++ , C # , Java e Scala .

Referências

  1. ^ Johnson, Ralph (26 de agosto de 1991). "Projetando classes reutilizáveis" (PDF) . www.cse.msu.edu .
  2. ^ Mike Mintz, Robert Ekendahl (2006). Verificação de hardware com C ++: um manual do profissional . Estados Unidos da América: Springer. p. 22. ISBN 978-0-387-25543-9.
  3. ^ Cook, William R .; Hill, Walter; Canning, Peter S. (1990). Herança não é subtipagem . Proc. 17º ACM SIGPLAN-SIGACT Symp. sobre Princípios de Linguagens de Programação (POPL). pp. 125–135. CiteSeerX 10.1.1.102.8635 . doi : 10.1145 / 96709.96721 . ISBN  0-89791-343-4.
  4. ^ Cardelli, Luca (1993). Programação tipificada (relatório técnico). Digital Equipment Corporation . p. 32–33. Relatório de Pesquisa SRC 45.
  5. ^ a b c Mikhajlov, Leonid; Sekerinski, Emil (1998). Um estudo do problema da classe base frágil (PDF) . Proc. 12ª Conf. Europeia em Programação Orientada a Objetos (ECOOP). Notas de aula em Ciência da Computação. 1445 . pp. 355–382. doi : 10.1007 / BFb0054099 . ISBN  978-3-540-64737-9.
  6. ^ Tempero, Ewan; Yang, Hong Yul; Noble, James (2013). O que os programadores fazem com herança em Java (PDF) . ECOOP 2013 – Programação Orientada a Objetos. pp. 577–601.
  7. ^ "Herança C ++" . www.cs.nmsu.edu .
  8. ^ Bjarne Stroustrup . O Design e a Evolução do C ++ . p. 417.
  9. ^ Herbert Schildt (2003). A referência completa C ++ . Tata McGrawhill Education Private Limited. p. 417 . ISBN 978-0-07-053246-5.
  10. ^ E Balagurusamy (2010). Programação orientada a objetos com C ++ . Tata McGrawhill Education Unip. Ltd. p. 213. ISBN 978-0-07-066907-9.
  11. ^ override (referência C #)
  12. ^ "GotW # 60: Projeto de classe com proteção de exceções, Parte 2: Herança" . Gotw.ca . Página visitada em 2012-08-15 .
  13. ^ Dr. KR Venugopal, Rajkumar Buyya (2013). Dominar C ++ . Tata McGrawhill Education Private Limited. p. 609. ISBN 9781259029943.
  14. ^ Cook, Hill & Canning 1990 .
  15. ^ Mitchell, John (2002). "10" Conceitos em linguagens orientadas a objetos " ". Conceitos em linguagem de programação . Cambridge, Reino Unido: Cambridge University Press. p. 287 . ISBN  978-0-521-78098-8.
  16. ^ a b c Holub, Allen (1º de agosto de 2003). “Porque estende é mal” . Retirado em 10 de março de 2015 .
  17. ^ Seiter, Linda M .; Palsberg, Jens; Lieberherr, Karl J. (1996). “Evolução do comportamento do objeto usando relações de contexto” . ACM SIGSOFT Notas de engenharia de software . 21 (6): 46. CiteSeerX 10.1.1.36.5053 . doi : 10.1145 / 250707.239108 . 
  18. ^ América, Pierre (1991). Projetar uma linguagem de programação orientada a objetos com subtipagem comportamental . REX School / Workshop sobre os fundamentos das linguagens orientadas a objetos. Notas de aula em Ciência da Computação. 489 . pp. 60–90. doi : 10.1007 / BFb0019440 . ISBN 978-3-540-53931-5.

Outras leituras