Classe base frágil - Fragile base class

O problema da classe base frágil é um problema arquitetônico fundamental de sistemas de programação orientados a objetos onde as classes básicas ( superclasses ) são consideradas "frágeis" porque modificações aparentemente seguras em uma classe base, quando herdadas pelas classes derivadas , podem causar o mau funcionamento das classes derivadas . O programador não pode determinar se uma mudança de classe base é segura simplesmente examinando isoladamente os métodos da classe base.

Uma solução possível é tornar as variáveis ​​de instância privadas para sua classe de definição e forçar as subclasses a usar acessadores para modificar os estados da superclasse. Uma linguagem também pode fazer com que as subclasses possam controlar quais métodos herdados são expostos publicamente. Essas mudanças evitam que as subclasses confiem nos detalhes de implementação das superclasses e permitem que as subclasses exponham apenas os métodos da superclasse que são aplicáveis ​​a si mesmas.

Outra solução alternativa seria ter uma interface em vez de uma superclasse.

O frágil problema da classe base foi responsabilizado pela recursão aberta (envio dinâmico de métodos ativado this), com a sugestão de que invocar métodos thispadrão para recursão fechada (envio estático, vinculação antecipada) em vez de recursão aberta (envio dinâmico, vinculação tardia), apenas usar recursão aberta quando for especificamente solicitada; chamadas externas (não usando this) seriam despachadas dinamicamente como de costume.

Exemplo de Java

O exemplo trivial a seguir foi escrito na linguagem de programação Java e mostra como uma modificação aparentemente segura de uma classe base pode causar o mau funcionamento de uma subclasse herdada ao inserir uma recursão infinita que resultará em um estouro de pilha .

class Super {

  private int counter = 0;

  void inc1() {
    counter++;
  }

  void inc2() {
    counter++;
  }

}

class Sub extends Super {

  @Override
  void inc2() {
    inc1();
  }

}

Chamar o método inc2 () ligado dinamicamente em uma instância de Sub aumentará corretamente o contador de campo em um. Se, no entanto, o código da superclasse for alterado da seguinte maneira:

class Super {

  private int counter = 0;

  void inc1() {
    inc2();
  }

  void inc2() {
    counter++;
  }
}

uma chamada para o método inc2 () ligado dinamicamente em uma instância de Sub causará uma recursão infinita entre ela e o método inc1 () da superclasse e, eventualmente, causará um estouro de pilha. Esse problema poderia ter sido evitado, declarando os métodos na superclasse como finais , o que tornaria impossível para uma subclasse substituí-los. No entanto, isso nem sempre é desejável ou possível. Portanto, é uma boa prática para superclasses evitar a alteração de chamadas para métodos vinculados dinamicamente.

Soluções

  • Objective-C tem categorias , bem como variáveis ​​de instância não frágeis .
  • Componente Pascal deprecates chamadas superclasse .
  • Java , C ++ (desde C ++ 11) e D permitem que a herança ou substituição de um método de classe seja proibida rotulando uma declaração de uma classe ou método, respectivamente, com a palavra-chave " final". No livro Effective Java , o autor Joshua Bloch escreve (no item 17) que os programadores devem "Projetar e documentar para herança ou então proibi-lo".
  • C # e VB.NET como Java têm palavras-chave de declaração de classe " sealed" e " Not Inheritable" para proibir a herança e exigem que uma subclasse use a palavra-chave " override" em métodos de substituição, a mesma solução posteriormente adotada por Scala.
  • Scala exige que uma subclasse use a palavra-chave " override" explicitamente para substituir um método da classe pai. No livro "Programação em Scala, 2ª edição", o autor escreve que (com modificações aqui) Se não houvesse o método f (), a implementação original do cliente do método f () não poderia ter um modificador de substituição. Depois de adicionar o método f () à segunda versão de sua classe de biblioteca, uma recompilação do código do cliente geraria um erro de compilação em vez de um comportamento incorreto.
  • No Kotlin, as classes e métodos são finais por padrão. Para habilitar a herança da classe, a classe deve ser marcada com o openmodificador. Da mesma forma, um método deve ser marcado openpara permitir a substituição do método.
  • Julia permite apenas a subtipagem de tipos abstratos e usa a composição como alternativa à herança . No entanto, tem envio múltiplo .

Veja também

Referências

links externos

  • Mikhajlov, Leonid; Sekerinski, Emil (1998). "Um estudo do problema da classe de base frágil" (PDF) . ECOOP'98 - Programação Orientada a Objetos . ECOOP 1998. LCNS . 1445 . pp. 355–382. doi : 10.1007 / BFb0054099 . ISSN  0302-9743 . QID  29543920 . Página visitada em 2020-07-21 .
  • Holub, Allen (1 de agosto de 2003). “Porque estende é mal” . Java Toolbox. JavaWorld . Página visitada em 2020-07-21 .