As classes / métodos abstratos estão obsoletos?

35

Eu costumava criar muitas classes / métodos abstratos. Então comecei a usar interfaces.

Agora, não tenho certeza se as interfaces não estão tornando as classes abstratas obsoletas.

Você precisa de uma aula totalmente abstrata? Crie uma interface em vez disso. Você precisa de uma classe abstrata com alguma implementação nela? Crie uma interface, crie uma classe. Herdar a classe, implemente a interface. Um benefício adicional é que algumas classes podem não precisar da classe pai, mas apenas implementarão a interface.

Então, classes / métodos abstratos são obsoletos?

    
por Boris Yankov 21.07.2011 / 17:39
fonte

11 respostas

111

Não.

As interfaces não podem fornecer implementação padrão, classes abstratas e método podem. Isso é especialmente útil para evitar a duplicação de código em muitos casos.

Esta é também uma maneira muito legal de reduzir o acoplamento sequencial. Sem método / classes abstratas, você não pode implementar o padrão de método de modelo. Eu sugiro que você veja este artigo da Wikipédia: link

    
por 21.07.2011 / 17:46
fonte
15

Existe um método abstrato para que você possa chamá-lo de dentro de sua classe base, mas implementá-lo em uma classe derivada. Então, sua classe base sabe:

public void DoTask()
{
    doSetup();
    DoWork();
    doCleanup();
}

protected abstract void DoWork();

Essa é uma maneira razoavelmente interessante de implementar um buraco no padrão do meio sem que a classe derivada saiba sobre as atividades de configuração e limpeza. Sem métodos abstratos, você teria que contar com a classe derivada implementando DoTask e lembrando de chamar base.DoSetup() e base.DoCleanup() o tempo todo.

Editar

Além disso, agradecemos a deadalnix por postar um link para o Template Method Pattern , que foi descrito acima sem realmente saber o nome. :)

    
por 21.07.2011 / 17:48
fonte
10

Não, eles não estão obsoletos.

Na verdade, existe uma diferença obscura, mas fundamental, entre Classes / Métodos e Interfaces Abstratas.

se o conjunto de classes em que um deles tem que ser usado tiver um comportamento comum que eles compartilham (classes relacionadas, quero dizer), então vá para classes / métodos abstratos.

Exemplo: balconista, oficial, diretor -todas as classes têmCalculateSalary () em comum, use classes base abstratas.CalculateSalary () pode ser implementado de forma diferente, mas existem outras coisas como GetAttendance () por exemplo que tem uma definição comum classe base.

Se suas classes não tiverem nada em comum (Classes não relacionadas, no contexto escolhido) entre elas, mas tiverem uma ação muito diferente na implementação, vá para Interface.

Exemplo: as classes cow, bench, car, telesope-not related mas Isortable podem estar lá para classificá-las em uma matriz.

Essa diferença é geralmente ignorada quando abordada de uma perspectiva polimórfica. Mas eu pessoalmente sinto que há situações em que um é mais apto do que o outro pela razão explicada acima.

    
por 21.07.2011 / 18:08
fonte
6

Além das outras boas respostas, há uma diferença fundamental entre interfaces e classes abstratas que ninguém mencionou especificamente, ou seja, que as interfaces são muito menos confiáveis e, portanto, impõem muito mais maior carga de teste do que classes abstratas. Por exemplo, considere este código C #:

public abstract class Frobber
{
    private Frobber() {}
    public abstract void Frob(Frotz frotz);
    private class GreenFrobber : Frobber
    { ... }
    private class RedFrobber : Frobber
    { ... }
    public static Frobber GetFrobber(bool b) { ... } // return a green or red frobber
}

public sealed class Frotz
{
    public void Frobbit(Frobber frobber)
    {
         ...
         frobber.Frob(this);
         ...
    }
}

Estou certo de que há apenas dois caminhos de código que preciso testar. O autor de Frobbit pode confiar no fato de que o frobador é vermelho ou verde.

Se, em vez disso, dissermos:

public interface IFrobber
{
    void Frob(Frotz frotz);
}
public class GreenFrobber : IFrobber
{ ... }
public class RedFrobber : Frobber
{ ... }

public sealed class Frotz
{
    public void Frobbit(IFrobber frobber)
    {
         ...
         frobber.Frob(this);
         ...
    }
}

Agora eu sei absolutamente nada sobre os efeitos dessa chamada para o Frob lá. Eu preciso ter certeza de que todo o código no Frobbit é robusto contra qualquer implementação possível do IFrobber , mesmo implementações por pessoas que são incompetentes (ruins) ou ativamente hostis para mim ou meus usuários (muito pior).

Classes abstratas permitem que você evite todos esses problemas; use-os!

    
por 21.07.2011 / 23:44
fonte
3

Você mesmo diz:

You need an abstract class with some implementation in it? Create an interface, create a class. Inherit the class, implement the interface

parece muito trabalho comparado a "herdar a classe abstrata". Você pode trabalhar sozinho abordando código de uma visão "purista", mas acho que já tenho o suficiente para fazer isso sem tentar adicionar à minha carga de trabalho sem nenhum benefício prático.

    
por 21.07.2011 / 17:49
fonte
3

Como comentei no post @deadnix: Implementações parciais são um antipadrão, apesar do fato de que o padrão de modelo as formaliza.

Uma solução limpa para este exemplo da Wikipédia sobre o padrão de modelo :

interface Game {
    void initialize(int playersCount);
    void makePlay(int player);
    boolean done();
    void finished();
    void printWinner();
}
class GameRunner {
    public void playOneGame(int playersCount, Game game) {
        game.initialize(playersCount);
        int j = 0;
        for (int i = 0; !game.finished(); i++)
             game.makePlay(i % playersCount);
        game.printWinner();
    }
} 
class Monopoly implements Game {
     //... implementation
}

Esta solução é melhor, porque usa a composição em vez da herança . O padrão de modelo introduz uma dependência entre a implementação das regras de Monopoly e a implementação de como os jogos devem ser executados. No entanto, estas são duas responsabilidades completamente diferentes e não há uma boa razão para acopla-las.

    
por 21.07.2011 / 23:47
fonte
2

Classes abstratas não são interfaces. São classes que não podem ser instanciadas.

You need a fully abstract class? Create an interface instead. You need an abstract class with some implementation in it? Create an interface, create a class. Inherit the class, implement the interface. An additional benefit is that some classes may not need the parent class, but will just implement the interface.

Mas você teria uma classe inútil não abstrata. São necessários métodos abstratos para preencher o furo de funcionalidade na classe base.

Por exemplo, dada essa classe

public abstract class Frobber {
    public abstract void Frob();

    public abstract boolean IsFrobbingNeeded { get; }

    public void FrobUntilFinished() {
        while (IsFrobbingNeeded) {
            Frob();
        }
    }
}

Como você implementaria essa funcionalidade básica em uma classe que não possui Frob() nem IsFrobbingNeeded ?

    
por 21.07.2011 / 17:47
fonte
2

Não. Até mesmo a alternativa proposta inclui o uso de classes abstratas. Além disso, como você não especificou o idioma, então vou em frente e digo que o código genérico é a melhor opção do que herança frágil. Classes abstratas têm vantagens significativas sobre interfaces.

    
por 21.07.2011 / 17:47
fonte
1

Eu sou um criador de estrutura de servlet onde as classes abstratas desempenham um papel essencial. Eu diria mais, eu preciso de métodos semi-abstractos, quando um método precisa ser sobrescrito em 50% dos casos e eu gostaria de ver um aviso do compilador sobre esse método não ter sido sobrescrito. Eu resolvo o problema ao adicionar anotações. Voltando à sua pergunta, há dois casos de uso diferentes de classes e interfaces abstratas, e ninguém está obsoleto até agora.

    
por 22.07.2011 / 02:36
fonte
0

Eu não acho que eles estejam obsoletos por interfaces, mas podem estar obsoletos pelo padrão de estratégia.

O uso primário de uma classe abstrata é adiar uma parte da implementação; para dizer: "esta parte da implementação da classe pode ser diferente".

Infelizmente, uma classe abstrata força o cliente a fazer isso via herança . Considerando que o padrão de estratégia permitirá que você alcance o mesmo resultado sem qualquer herança. O cliente pode criar instâncias de sua classe em vez de definir sempre as próprias e a "implementação" (comportamento) da classe pode variar dinamicamente. O padrão de estratégia tem a vantagem adicional de que o comportamento pode ser alterado em tempo de execução, não apenas em tempo de design, e também possui um acoplamento significativamente mais fraco entre os tipos envolvidos.

    
por 06.04.2017 / 23:04
fonte
0

Eu geralmente enfrentei, de longe, mais problemas de manutenção associados a interfaces puras do que ABCs, até mesmo ABCs usados com herança múltipla. YMMV - não sei, talvez nossa equipe tenha usado de forma inadequada.

Dito isto, se usarmos uma analogia do mundo real, quanto de uso existe para interfaces puras completamente desprovidas de funcionalidade e estado? Se eu usar o USB como exemplo, é uma interface razoavelmente estável (acho que estamos no USB 3.2 agora, mas também tem mantido a compatibilidade com versões anteriores).

No entanto, não é uma interface sem estado. Não é desprovido de funcionalidade. É mais como uma classe base abstrata do que uma interface pura. Na verdade, ele está mais próximo de uma classe concreta com requisitos funcionais e de estado muito específicos, sendo a única abstração que se conecta à porta sendo a única parte substituível.

Caso contrário, seria apenas um "buraco" em seu computador com um formato padronizado e requisitos funcionais muito mais frouxos que não fariam nada por conta própria até que todos os fabricantes criassem seu próprio hardware para fazer esse furo, ponto em que se torna um padrão muito mais fraco e nada mais do que um "buraco" e uma especificação do que deve ser feito, mas nenhuma disposição central de como fazê-lo. Enquanto isso, podemos acabar com 200 maneiras diferentes de fazê-lo, depois que todos os fabricantes de hardware tentam criar suas próprias maneiras de anexar a funcionalidade e declarar esse "buraco".

E, nesse ponto, podemos ter certos fabricantes que apresentam problemas diferentes em relação a outros. Se precisarmos atualizar a especificação, poderemos ter 200 implementações de portas USB de concreto diferentes, com maneiras totalmente diferentes de lidar com a especificação que precisa ser atualizada e testada. Alguns fabricantes podem desenvolver implementações padrão que compartilham entre si (sua classe analógica implementando essa interface), mas não todas. Algumas versões podem ser mais lentas que outras. Alguns podem ter melhor rendimento, mas pior latência ou vice-versa. Alguns podem usar mais energia da bateria do que outros. Alguns podem falhar e não funcionar com todo o hardware que deveria funcionar com portas USB. Alguns podem exigir que um reator nuclear seja anexado para operar, o que tem uma tendência a causar envenenamento por radiação a seus usuários.

E é isso que eu encontrei pessoalmente com interfaces puras. Pode haver alguns casos em que eles fazem sentido, como apenas modelar o fator de forma de uma placa-mãe contra um gabinete da CPU. As analogias do fator de forma são, na verdade, praticamente sem estado e desprovidas de funcionalidade, como no "buraco" analógico. Mas muitas vezes considero um grande erro as equipas considerarem que, de alguma forma, são superiores em todos os casos, nem mesmo perto.

Pelo contrário, acho que muito mais casos seriam resolvidos melhor pelo ABC do que pelas interfaces, se essas são as duas opções, a menos que sua equipe seja tão gigantesca que seja realmente desejável ter o equivalente analógico acima de 200 implementações USB concorrentes em vez de uma padrão central para manter. Em um ex-time em que eu estava, na verdade eu tive que lutar muito apenas para liberar o padrão de codificação para permitir ABCs e herança múltipla, e principalmente em resposta a esses problemas de manutenção descritos acima.

    
por 19.12.2017 / 05:31
fonte