web-dev-qa-db-fra.com

Les classes / méthodes abstraites sont-elles obsolètes?

Je créais beaucoup de classes/méthodes abstraites. Ensuite, j'ai commencé à utiliser des interfaces.

Maintenant, je ne sais pas si les interfaces ne font pas de classes abstraites obsolètes.

Vous avez besoin d'une classe entièrement abstraite? Créez une interface à la place. Vous avez besoin d'une classe abstraite avec une implémentation? Créez une interface, créez une classe. Hériter de la classe, implémentez l'interface. Un avantage supplémentaire est que certaines classes n'ont pas besoin de la classe mère, mais ne mettront que mettre en œuvre l'interface.

Alors, sont des classes abstraites/méthodes obsolètes?

37
Boris Yankov

Non.

Les interfaces ne peuvent pas fournir de mise en œuvre par défaut, de classes abstraites et de méthodes peuvent. Ceci est particulièrement utile pour éviter la duplication de code dans de nombreux cas.

C'est aussi un moyen très agréable de réduire le couplage séquentiel. Sans méthode/classes abstraites, vous ne pouvez pas implémenter le modèle de méthode de modèle. Je vous suggère de regarder cet article de Wikipedia: http://fr.wikipedia.org/wiki/template_mesthod_pattern

111
deadalnix

Une méthode abstraite existe pour que vous puissiez l'appeler à partir de votre classe de base, mais mettez-la dans une classe dérivée. Donc, votre classe de base sait:

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

protected abstract void DoWork();

C'est une façon raisonnablement agréable de mettre en œuvre un trou dans le modèle intermédiaire sans la classe dérivée de savoir sur les activités de configuration et de nettoyage. Sans méthodes abstraites, vous devez compter sur la classe dérivée implémentant DoTask et rappelez-vous d'appeler base.DoSetup() et base.DoCleanup() tout le temps.

Edit

De plus, grâce à Deadalnix pour avoir publié un lien vers le modèle de méthode de modèle , qui est ce que j'ai décrit ci-dessus sans connaître le nom. :)

15
Scott Whitlock

Non, ils ne sont pas obsolètes.

En fait, il existe une différence obscure mais fondamentale entre les classes/méthodes abstraites et les interfaces.

si l'ensemble des classes dans lesquelles l'une d'entre elles doit être utilisé a un comportement courant qu'ils partagent (classes connexes, je veux dire), puis optez pour des classes/méthodes abstraites.

Exemple: greffier, officier, directeur - Tous ces classes ont des classes de base () en commun, utilisent des classes de base abstrait.Calculéalaires () peuvent être appliquées différemment, mais il existe certaines autres choses comme Getattendance (), par exemple, qui a une définition commune dans la classe de base.

Si vos classes n'ont rien de commun (classes non liées, dans le contexte choisi) entre eux, mais a une action très différente de la mise en œuvre, puis optez pour l'interface.

Exemple: la vache, le banc, la voiture, les classes non liées de Telesope, mais isortable peuvent être là pour les trier dans un tableau.

Cette différence est généralement ignorée lorsqu'elle est approchée d'une perspective polymorphe. Mais je pense personnellement qu'il y a des situations où l'on est une apparence que l'autre pour la raison expliquée ci-dessus.

10
WinW

En plus des autres bonnes réponses, il existe une différence fondamentale entre les interfaces et les classes abstraites que personne n'a notamment mentionné, à savoir que les interfaces sont beaucoup moins fiable et donc imposer beaucoup plus grand test de test que des classes abstraites. Par exemple, considérons ce code 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);
         ...
    }
}

Je suis garanti qu'il n'y ait que deux Chemins de code que j'ai besoin de tester. L'auteur de Frobbit peut compter sur le fait que la frobber est rouge ou vert.

Si au lieu de cela, nous disons:

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);
         ...
    }
}

Je sais maintenant absolument rien sur les effets de cet appel à FRB. Je dois être sûr que tout le code dans Frobbit est robuste toute mise en œuvre possible de ifrobber, même des implémentations de personnes qui sont incompétent (mauvais) ou activement hostile pour moi ou mes utilisateurs (bien pire).

Les classes abstraites vous permettent d'éviter tous ces problèmes; Utilise les!

6
Eric Lippert

Comme je l'ai commenté sur @deadnix Post: Les implémentations partielles sont un anti-motif, malgré le fait que le modèle de modèle les formalise.

Une solution propre pour cela Exemple Wikipedia du modèle de modèle :

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
}

Cette solution est meilleure, car elle utilise composition au lieu d'héritage . Le modèle de modèle introduit une dépendance entre la mise en œuvre des règles monopolistiques et la mise en œuvre de la manière dont les jeux doivent être exécutés. Cependant, ce sont deux responsabilités totalement différentes et il n'y a pas de bonne raison de les coupler.

4
back2dos

Vous le dites vous-même:

Vous avez besoin d'une classe abstraite avec une implémentation? Créez une interface, créez une classe. Hériter de la classe, implémentez l'interface

cela semble beaucoup de travail comparé à "hériter de la classe abstraite". Vous pouvez faire du travail pour vous-même en approchant du code d'une vue "puriste", mais je trouve que j'ai assez à faire sans essayer d'ajouter à ma charge de travail sans avantage pratique.

4
gbjbaanb

Non. Même votre alternative proposée inclut l'utilisation de classes abstraites. De plus, puisque vous n'avez pas précisé la langue, je vais aller droit à l'avance et dire que le code générique est la meilleure option que la héritage fragile de toute façon. Les classes abstraites ont des avantages significatifs sur les interfaces.

2
DeadMG

Je suis un créateur de cadre de servlet où les classes abstraites jouent un rôle essentiel. Je dirais davantage, j'ai besoin de méthodes semi-abstraites, lorsqu'une méthode doit être remplacée dans 50% de cas et j'aimerais voir l'avertissement du compilateur à propos de cette méthode n'était pas remplacée. Je résout le problème en ajoutant des annotations. Retournez à votre question, il existe deux utilisations différentes de classes abstraites et d'interfaces, et personne n'est obsolète jusqu'à présent.

1
Dmitriy R

Les classes abstraites ne sont pas des interfaces. Ce sont des cours qui ne peuvent pas être instanciés.

Vous avez besoin d'une classe entièrement abstraite? Créez une interface à la place. Vous avez besoin d'une classe abstraite avec une implémentation? Créez une interface, créez une classe. Hériter de la classe, implémentez l'interface. Un avantage supplémentaire est que certaines classes n'ont pas besoin de la classe mère, mais ne mettront que mettre en œuvre l'interface.

Mais alors vous auriez une classe inutile non abstraite. Des méthodes abstraites sont nécessaires pour remplir le trou de fonctionnalité dans la classe de base.

Par exemple, étant donné cette classe

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

    public abstract boolean IsFrobbingNeeded { get; }

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

Comment implémenteriez-vous cette fonctionnalité de base dans une classe qui n'a ni Frob() ni IsFrobbingNeeded?

1
configurator

Je ne pense pas qu'ils sont obsolètes par des interfaces, mais ils peuvent être obsolètes par le modèle de stratégie.

L'utilisation principale d'une classe abstraite consiste à reporter une partie de la mise en œuvre; Dire: "Cette partie de la mise en œuvre de la classe peut être faite différente".

Malheureusement, une classe abstraite force le client à faire cela via héritage. ATTENDU QUE le modèle de stratégie vous permettra d'obtenir le même résultat sans héritage. Le client peut faire des cas de votre classe plutôt que de toujours définir leur propre, et la "mise en œuvre" de la classe (comportement) peut varier de manière dynamique. Le modèle de stratégie a augmenté supplémentaire que le comportement peut être modifié au moment de l'exécution, non seulement au moment de la conception, et a également un couplage de manière significative entre les types impliqués.

0
Dave Cousineau

Je suis généralement confronté loin de loin, de loin, plus de problèmes d'entretien associés à des interfaces pure que les ABC, même des abcs utilisés avec de multiples héritages. YMMV - Dunno, peut-être que notre équipe vient de les utiliser inadéquatement.

Cela dit, si nous utilisons une analogie du monde réel, la quantité d'utilisation est là pour des interfaces pures complètement dépourvues de fonctionnalité et d'état? Si j'utilise USB comme exemple, c'est une interface raisonnablement stable (je pense que nous sommes à USB 3.2 maintenant, mais il a également maintenu la compatibilité à l'envers).

Pourtant, ce n'est pas une interface apatride. Ce n'est pas dépourvu de fonctionnalité. Cela ressemble plus à une classe de base abstraite qu'une interface pure. Il est réellement plus proche d'une classe de béton avec des exigences fonctionnelles et fonctionnelles très spécifiques, avec la seule abstraction étant ce qui se branche dans le port étant la seule partie substituable.

Sinon, il s'agirait simplement d'un "trou" de votre ordinateur avec un facteur de forme standardisé et des exigences fonctionnelles beaucoup plus lâches qui ne feraient rien à lui-même tant que chaque fabricant a eu lieu avec son propre matériel pour que ce trou fait quelque chose, à quel point Cela devient une norme beaucoup plus faible et rien de plus qu'un "trou" et une spécification de ce qu'il devrait faire, mais aucune disposition centrale pour la façon de le faire. Pendant ce temps, nous pourrions nous retrouver avec 200 façons différentes de le faire après que tous les fabricants de matériel essaient de trouver leurs propres moyens d'attacher des fonctionnalités et de l'état à ce "trou".

Et à ce stade, nous pourrions avoir certains fabricants qui introduisent différents problèmes sur les autres. Si nous devons mettre à jour la spécification, nous pourrions disposer de 200 implémentations de port USB différentes avec des moyens totalement différents de résoudre les spécifications devant être mises à jour et testées. Certains fabricants pourraient développer des implémentations standard de facto qu'elles partagent entre elles (votre classe de base analogique mettant en œuvre cette interface), mais pas toutes. Certaines versions pourraient être plus lentes que d'autres. Certains pourraient avoir un meilleur débit mais pire latence ou vice versa. Certains pourraient utiliser plus d'énergie de la batterie que d'autres. Certains pourraient se détendre et ne pas travailler avec tout le matériel censé travailler avec des ports USB. Certains pourraient nécessiter un réacteur nucléaire pour être attaché à fonctionner, ce qui a tendance à donner une intoxication sur le rayonnement de ses utilisateurs.

Et c'est ce que j'ai trouvé, personnellement, avec des interfaces pures. Il pourrait y avoir des cas où ils ont un sens, comme pour modéliser le facteur de forme d'une carte mère contre une affaire de la CPU. Les analogies facteurs de formulaire sont en effet assez apatrides et dépourvues de fonctionnalité, comme avec le "trou" analogique. Mais je considère souvent que cela est une énorme erreur pour les équipes de considérer que d'être supérieurs en quelque sorte dans tous les cas, pas même de près.

Au contraire, je pense que beaucoup plus de cas seraient mieux résolus par ABCS que des interfaces si ce sont les deux choix que si votre équipe est si gigantesque qu'elle est réellement souhaitable d'avoir l'équivalent analogique supérieur à 200 mises en œuvre concurrentes de la USB plutôt que d'une norme centrale à maintenir. Dans une ancienne équipe, j'étais dans l'équipe, je devais effectivement de me battre juste pour desserrer la norme de codage pour permettre à ABC et à une héritage multiple, et principalement en réponse à ces problèmes de maintenance décrits ci-dessus.

0
user204677