Quando usar a herança

A herança é um conceito de programação útil, mas é fácil de usar inadequadamente.Interfaces com frequência fazem um trabalho melhor.Este tópico e Quando usar interfaces ajudam você a compreender quando cada abordagem deve ser usada.

A herança é uma boa escolha quando:

  • A hierarquia de herança representa um relacionamento "é um" e não um relacionamento "tem um".

  • Você pode reutilizar o código de classes base.

  • Você precisa aplicar a mesma classe e métodos para tipos diferentes de dados.

  • A hierarquia de classe é razoavelmente superficial, e não é provável que outros desenvolvedores adicionem muitos mais níveis.

  • Você deseja fazer alterações globais para classes derivadas alterando uma classe base.

Essas considerações são discutidas em ordem abaixo.

Herança e relacionamentos "É um"

Duas maneiras para mostrar relações de classe na programação orientada a objetos são relacionamentos "é um" e "tem um".Em um relacionamento "é um", a classe derivada é claramente uma espécie da classe base.Por exemplo, uma classe denominada PremierCustomer representa uma relação "é um" com uma classe base chamada Customer porque um cliente premier é um cliente.No entanto, uma classe denominada CustomerReferral representa uma relação "tem um" com a classe Customer porque uma referência de cliente tem um cliente, mas uma referência de cliente não é uma espécie de cliente.

Objetos em uma hierarquia de herança devem ter uma relação "é um" com sua classe base porque eles herdam os campos, propriedades, métodos e os eventos definidos na classe base.Classes que representam uma relação "tem um" com outras classes não são adequados para hierarquias de herança porque elas podem herdar propriedades e métodos inadequados.Por exemplo, se a classe CustomerReferral fosse derivada da classe Customer discutida anteriormente, ela poderia herdar propriedades que não fazem nenhum sentido, como ShippingPrefs e LastOrderPlaced. "Tem um "relações sistema autônomo isso devem ser representadas usando interfaces ou classes não relacionados.A ilustração a seguir mostra exemplos de ambos relacionamentos "é um" e "tem um".

Classes base e reutilização de códigos

Outro motivo para usar a herança é a vantagem de reutilização de código.Classes com bom design podem ser depuradas uma vez e usadas repetidamente como uma base para novas classes.

Um exemplo comum de reutilização de código eficaz está conectado com bibliotecas que gerenciam estruturas de dados.Suponha, por exemplo, que você tenha um aplicativo grande de empresa que gerencia vários tipos de listas na memória.Uma é uma cópia na memória do seu banco de dados de clientes, lida a partir de um banco de dados no início da sessão para ganhar velocidade.A estrutura de dados pode ser algo parecido com o seguinte:

Class CustomerInfo
    Protected PreviousCustomer As CustomerInfo
    Protected NextCustomer As CustomerInfo
    Public ID As Integer
    Public FullName As String

    Public Sub InsertCustomer(ByVal FullName As String)
        ' Insert code to add a CustomerInfo item to the list.
    End Sub

    Public Sub DeleteCustomer()
        ' Insert code to remove a CustomerInfo item from the list.
    End Sub

    Public Function GetNextCustomer() As CustomerInfo
        ' Insert code to get the next CustomerInfo item from the list.
        Return NextCustomer
    End Function

    Public Function GetPrevCustomer() As CustomerInfo
        'Insert code to get the previous CustomerInfo item from the list.
        Return PreviousCustomer
    End Function
End Class

Seu aplicativo também pode ter uma lista semelhante de produtos que o usuário adicionou à uma lista de carrinho de compras, conforme mostrado no fragmento de código a seguir:

Class ShoppingCartItem
    Protected PreviousItem As ShoppingCartItem
    Protected NextItem As ShoppingCartItem
    Public ProductCode As Integer
    Public Function GetNextItem() As ShoppingCartItem
        ' Insert code to get the next ShoppingCartItem from the list.
        Return NextItem
    End Function
End Class

Você pode ver um padrão aqui: duas listas se comportam da mesma maneira (inserções, exclusões e recuperações), mas operam em diferentes tipos de dados.Manter duas bases de código para executar basicamente as mesmas funções não é eficiente.A solução mais eficiente é incluir o gerenciamento de listas em sua própria classe e, em seguida, herdar essa classe para diferentes tipos de dados:

Class ListItem
    Protected PreviousItem As ListItem
    Protected NextItem As ListItem
    Public Function GetNextItem() As ListItem
        ' Insert code to get the next item in the list.
        Return NextItem
    End Function
    Public Sub InsertNextItem()
        ' Insert code to add a item to the list.
    End Sub

    Public Sub DeleteNextItem()
        ' Insert code to remove a item from the list.
    End Sub

    Public Function GetPrevItem() As ListItem
        'Insert code to get the previous item from the list.
        Return PreviousItem
    End Function
End Class

A classe ListItem precisa ser depurada apenas uma vez.Em seguida, você pode criar classes que a usam sem nunca ter de pensar sobre gerenciamento de lista novamente.Por exemplo:

Class CustomerInfo
    Inherits ListItem
    Public ID As Integer
    Public FullName As String
End Class
Class ShoppingCartItem
    Inherits ListItem
    Public ProductCode As Integer
End Class

Embora a reutilização de código baseada em herança seja uma poderosa ferramenta, ela também possui riscos associados.Mesmo os sistemas mais bem projetados, às vezes, se alteram de maneiras que os designers não puderam prever.Alterações em uma hierarquia de classe existente podem, às vezes, ter consequências não esperadas; alguns exemplos são discutidos em "O problema da classe base f?agil," em Alterações de Design de Classe Base Após a Implementação.

Classes Derivadas intercambiáveis

Classes derivadas de uma hierarquia de classe podem, às vezes, ser usadas de forma intercambiável com suas classes base, um processo chamado de polimorfismo baseado em herança.Essa abordagem combina os melhores recursos do polimorfismo baseado na interface com a opção de reutilizar ou substituir o código de uma classe base.

Um exemplo em que isso pode ser útil é em um pacote de desenho.Por exemplo, considere o fragmento de código a seguir, que não usa a herança:

Sub Draw(ByVal Shape As DrawingShape, ByVal X As Integer, _
    ByVal Y As Integer, ByVal Size As Integer)

    Select Case Shape.type
        Case shpCircle
            ' Insert circle drawing code here.
        Case shpLine
            ' Insert line drawing code here.
    End Select
End Sub

Essa abordagem apresenta alguns problemas.Se alguém decidir adicionar uma opção da elipse posteriormente, será necessário alterar o código-fonte; é possível que os usuários de destino sequer tenham acesso ao código-fonte.Um problema mais sutil é que desenhar uma elipse requer outro parâmetro (elipses têm um diâmetro principal e um secundário) que poderia ser irrelevante no caso da linha.Se alguém deseja, então, adicionar uma polilinha (várias linhas conectadas), então outro parâmetro seria adicionado, e ele seria irrelevante para os outros casos.

A herança resolve a maioria desses problemas.Classes base bem-estruturadas deixam a implementação de métodos específicos para as classes derivadas, de modo que qualquer tipo de forma pode ser acomodado.Outros desenvolvedores podem implementar métodos em classes derivadas usando a documentação para a classe base.Outros itens de classe (como as coordenadas x e y) podem ser criados dentro da classe base porque todos os descendentes os usam.Por exemplo, Draw poderia ser um método MustOverride:

MustInherit Class Shape
    Public X As Integer
    Public Y As Integer
    MustOverride Sub Draw()
End Class

Assim, você poderia adicionar a essa classe conforme apropriado para diferentes formas.Por exemplo, uma classe Line talvez só precise um campo Length:

Class Line
    Inherits Shape
    Public Length As Integer
    Overrides Sub Draw()
        ' Insert code here to implement Draw for this shape.
    End Sub
End Class

Essa abordagem é útil porque outros desenvolvedores, que não têm acesso ao seu código-fonte, podem estender a classe base com novas classes derivadas conforme necessário.Por exemplo, uma classe denominada Rectangle pode ser derivada da classe Line:

Class Rectangle
    Inherits Line
    Public Width As Integer
    Overrides Sub Draw()
        ' Insert code here to implement Draw for the Rectangle shape.
    End Sub
End Class

Este exemplo mostra como você pode se mover a partir de classes de uso geral para classes muito específicas, adicionando os detalhes de implementação em cada nível.

Nesta altura talvez seja bom reavaliar se a classe derivada realmente representa um relacionamento "é um" ou, em vez disso, um relacionamento "tem um".Se a nova classe retângulo for apenas composta de linhas, então a herança não é a melhor opção.No entanto, se o novo retângulo é uma linha com uma propriedade de largura, então o relacionamento "é um" é mantido.

Hierarquias de classe superficiais

A herança é mais adequada para hierarquias de classe relativamente superficiais.Hierarquias de classe excessivamente longas e complexas podem ser difíceis de desenvolver.A decisão de usar uma hierarquia de classe envolve ponderar os benefícios do uso de uma hiararquia de classe contra a complexidade.Como regra geral, você deve limitar hierarquias para seis níveis ou menos.No entanto, o tamanho máximo de qualquer hierarquia de classe depende de inúmeros fatores, incluindo a quantidade de complexidade em cada nível.

Alterações globais para classes derivadas através da classe base

Um dos mais poderosos recursos de herança é a capacidade para fazer alterações em uma classe base que se propagam para classes derivadas.Quando usada com cuidado, você pode atualizar a implementação de um único método, e dezenas — ou mesmo centenas — de classes derivadas podem usar o novo código.No entanto, isso pode ser uma prática perigosa porque essas alterações podem causar problemas com classes herdadas desenvolvidas por outras pessoas.Deve-se ter cuidado para garantir que a nova classe base é compatível com as classes que usam a original.Você deve evitar especificamente alterar o nome ou o tipo dos membros da classe base participantes.

Suponha, por exemplo, que você crie uma classe base com um campo de tipo Integer para armazenar informações de CEP, e outros desenvolvedores criaram classes derivadas que usam o campo CEP herdado.Suponha além disso que o campo CEP armazena cinco dígitos, e a agência postal expandiu os CEPs com um hífen e mais quatro dígitos.No pior caso, você pode modificar o campo na classe base para armazenar um sequência de 10 caracteres, mas outros desenvolvedores precisariam alterar e recompilar as classes derivadas para usar o tamanho e tipo de dados novos.

A maneira mais segura de alterar uma classe base é simplesmente adicionar novos membros.Por exemplo, você pode adicionar um novo campo para armazenar os quatro dígitos adicionais no exemplo do CEP discutido anteriormente.Dessa forma, os aplicativos cliente podem ser atualizados para usar o novo campo sem quebrar aplicativos existentes.A capacidade de estender classes base em uma hierarquia de herança é um importante benefício que não existe com interfaces.

Consulte também

Conceitos

Quando usar interfaces

Alterações de Design de Classe Base Após a Implementação