Todos os métodos públicos em uma classe abstrata devem ser marcados como virtuais?

12

Recentemente, tive que atualizar uma classe base abstrata em alguns OSS que estava usando, para que fosse mais testável, tornando-os virtuais (não era possível usar uma interface porque combinava duas). Isso me fez pensar se eu deveria marcar todos os métodos que eu precisava como virtuais ou se eu deveria marcar todos os métodos / propriedades públicas como virtuais. Eu geralmente concordo com Roy Osherove que todo método deve ser virtualizado, mas me deparei com este artigo que me fez pensar se isso era necessário ou não . No entanto, vou limitar isso a classes abstratas por simplicidade (se todos os métodos públicos concretos devem ser virtuais é especialmente discutível, tenho certeza).

Pude ver onde você pode permitir que uma subclasse use um método, mas não que ele substitua a implementação. No entanto, desde que você confie que o Princípio da Substituição de Liskov será seguido, por que você não permitiria que ele fosse vencido? Ao marcá-lo como abstrato, você está forçando uma certa substituição de qualquer maneira; portanto, parece-me que todos os métodos públicos dentro de uma classe abstrata devem ser marcados como virtuais.

No entanto, eu queria perguntar caso houvesse algo que eu não estivesse pensando. Todos os métodos públicos dentro de uma classe abstrata devem ser virtualizados?

Justin Pihony
fonte
Isso pode ajudar: stackoverflow.com/questions/391483/…
NoChance
1
Talvez você deva analisar por que, em C #, o padrão não é virtual, mas em Java, é. A razão pela qual provavelmente lhe daria algumas dicas sobre a resposta.
Daniel Little

Respostas:

9

No entanto, desde que você confie que o Princípio da Substituição de Liskov será seguido, por que você não permitiria que ele fosse vencido?

Por exemplo, porque eu quero que a implementação esquelética de um algoritmo seja corrigida e permita apenas que partes específicas sejam (re) definidas por subclasses. Isso é amplamente conhecido como padrão do método de modelo (ênfase abaixo por mim):

O método de modelo, portanto, gerencia a imagem maior da semântica de tarefas e detalhes de implementação mais refinados da seleção e sequência dos métodos. Essa imagem maior chama métodos abstratos e não abstratos para a tarefa em questão. Os métodos não abstratos são completamente controlados pelo método de modelo, mas os métodos abstratos, implementados nas subclasses, fornecem o poder expressivo e o grau de liberdade do padrão. Alguns ou todos os métodos abstratos podem ser especializados em uma subclasse, permitindo que o gravador da subclasse forneça um comportamento específico com modificações mínimas na semântica maior. O método do modelo (que não é abstrato) permanece inalterado nesse padrão, garantindo que os métodos não abstratos subordinados e os métodos abstratos sejam chamados na sequência pretendida originalmente.

Atualizar

Alguns exemplos concretos de projetos nos quais tenho trabalhado:

  1. comunicação com um sistema legado de mainframe através de várias "telas". Cada tela possui vários campos, de nome fixo, posição e comprimento, contendo bits de dados específicos. Uma solicitação preenche determinados campos com dados específicos. Uma resposta retorna dados em um ou mais outros campos. Cada transação segue a mesma lógica básica, mas os detalhes são diferentes em todas as telas. Utilizamos o Template Method em vários projetos diferentes para implementar o esqueleto fixo da lógica de manipulação de tela, enquanto permitimos às subclasses definir os detalhes específicos da tela.
  2. exportando / importando dados de configuração em tabelas de banco de dados para / de arquivos do Excel. Novamente, o esquema básico de processar um arquivo do Excel e inserir / atualizar registros do banco de dados ou despejar os registros no Excel é o mesmo para cada tabela, mas os detalhes de cada tabela são diferentes. Portanto, o Template Method é uma escolha muito óbvia para eliminar duplicatas de código e facilitar a compreensão e manutenção do código.
  3. Gerando documentos PDF. Cada documento tem a mesma estrutura, mas seu conteúdo é diferente a cada vez, dependendo de muitos fatores. Mais uma vez, o Template Method facilita a separação do esqueleto fixo do algoritmo de geração dos detalhes mutáveis ​​e específicos do caso. De fato. ele ainda se aplica a vários níveis aqui, pois o documento consiste em várias seções , cada uma das quais consiste em zero ou mais campos . O Método do Modelo é aplicado em 3 níveis distintos aqui.

Nos dois primeiros casos, a implementação legada original usou a Strategy , resultando em muitos códigos duplicados, que, com o passar dos anos, cresceram sutis diferenças aqui e ali, continham muitos bugs duplicados ou ligeiramente diferentes e eram muito difíceis de manter. A refatoração para o Método do modelo (e alguns outros aprimoramentos, como o uso de anotações em Java) reduziu o tamanho do código em cerca de 40 a 70%.

Estes são apenas os exemplos mais recentes que me vêm à mente. Eu poderia citar mais casos de quase todos os projetos em que tenho trabalhado até agora.

Péter Török
fonte
Simplesmente citar o GoF não é uma resposta. Você precisaria dar um motivo real para fazer isso.
DeadMG
@DeadMG, eu tenho usado o Método de Modelo regularmente durante minha carreira, então achei óbvio que é um padrão muito prático e útil (como a maioria dos padrões do GoF é ... esses não são exercícios acadêmicos teóricos, mas coletados da experiência do mundo real). Mas, aparentemente, nem todos concordam com isso ... por isso, adiciono alguns exemplos concretos à minha resposta.
Péter Török
2

É perfeitamente razoável e, às vezes, desejável ter métodos não virtuais em uma classe base abstrata; só porque é uma classe abstrata, não significa necessariamente que cada parte dela deva ser utilizável polimorficamente.

Por exemplo, convém usar o idioma 'Polimorfismo não virtual', no qual uma função é chamada polimorficamente a partir de uma função membro não virtual, para garantir que certas condições ou pós-condições sejam atendidas antes que a função virtual seja chamada

class MyAbstractBaseClass
{
protected:
    virtual void OverrideMe() = 0;
public:
    void CallMeFirst();

    void CallMe()
    {
        CallMeFirst();
        OverrideMe();
    }
};
Ben Cottrell
fonte
Eu argumentaria que, desde que o comportamento geral permaneça o mesmo, isso poderá ser virtualizado. Você pode preencher as condições pré / pós usando uma implementação diferente (banco de dados x memória). Caso contrário, deve ser uma função privada?
Justin Pihony
2

É suficiente para uma classe conter UM método virtual, para que a classe se torne abstrata. Você pode prestar atenção em quais métodos deseja virtual e quais não, de acordo com o tipo de polimorfismo que planeja usar.

aplacar
fonte
1

Pergunte a si mesmo qual é o uso de um método não virtual em uma classe abstrata. Esse método teria que ter uma implementação para torná-lo útil. Mas se a classe tem uma implementação, ela ainda pode ser chamada de classe abstrata? Mesmo que o idioma / compilador permita, isso faz sentido? Pessoalmente, acho que não. Você teria uma classe normal com métodos abstratos que os descendentes devem implementar.

Meu idioma principal é Delphi, não c #. No Delphi, se você marcar um resumo do método, também precisará marcá-lo como virtual ou o compilador reclamará. Não acompanhei as alterações mais recentes da linguagem de perto, mas se as classes abstratas estiverem no Delphi ou eu esperaria que o compilador se queixasse de métodos não virtuais, métodos particulares e implementações de métodos para uma classe marcada como abstract no nível da classe.

Marjan Venema
fonte
2
Eu acho que faz todo sentido ter uma classe abstrata que tenha alguns métodos abstratos, alguns métodos virtuais e outros não virtuais. Eu acho que sempre depende do que exatamente você quer fazer.
svick
3
Primeiro eu vou revisar seus c # q's. Um método abstrato em C # é implicitamente virtual e não pode ter uma implementação. Quanto ao seu primeiro ponto, uma classe abstrata em C # pode e deve ter uma implementação de algum tipo (caso contrário, você deve apenas usar uma interface). O ponto de uma classe ser abstrata é que DEVE ser subclassificada, porém contém lógica que (teoricamente) todas as subclasses usarão. Isso reduz a duplicação de código. O que estou perguntando é se alguma dessas implementações deve ser impedida de ser anulada (essencialmente dizendo que o caminho base é o único).
Justin Pihony
@ JustinPihony: obrigado. No Delphi, quando você marca um resumo do método, o compilador irá reclamar se você fornecer uma implementação para ele. Interessante como linguagens diferentes implementam conceitos de maneira diferente e, assim, criam expectativas diferentes em seus usuários.
Marjan Venema
@svick: sim, faz todo o sentido, eu simplesmente não chamaria de classe abstrata, mas uma classe com métodos abstratos ... Mas acho que essa pode ser apenas a minha interpretação.
Marjan Venema
@MarjanVenema, mas isso não é terminologia que o C # usa. Se você marcar algum método em uma classe abstract, também deverá marcar a classe inteira abstract.
svick
0

No entanto, desde que você confie que o Princípio da Substituição de Liskov será seguido,> por que você não permitiria que ele fosse vencido?

Você não virtualiza certos métodos porque não acredita que esse seja o caso. Além disso, ao tornar certos métodos não virtuais, você está sinalizando para os herdeiros que métodos devem ser implementados.

Pessoalmente, muitas vezes tornarei as sobrecargas de método existentes por conveniência, não virtuais, para que os usuários da classe possam ter padrões consistentes e os implementadores nem sequer cometerem o erro de quebrar esse comportamento implícito.

Telastyn
fonte