Quand utiliser des classes abstraites au lieu d'interfaces avec des méthodes d'extension en C #?


70

"Classe abstraite" et "interface" sont des concepts similaires, l'interface étant la plus abstraite des deux. Un facteur de différenciation est que les classes abstraites fournissent des implémentations de méthodes pour les classes dérivées lorsque cela est nécessaire. En C #, toutefois, ce facteur de différenciation a été réduit par l’introduction récente de méthodes d’extension, qui permettent de fournir des implémentations pour les méthodes d’interface. Un autre facteur de différenciation est qu'une classe ne peut hériter que d'une classe abstraite (c'est-à-dire qu'il n'y a pas d'héritage multiple), mais qu'elle peut implémenter plusieurs interfaces. Cela rend les interfaces moins restrictives et plus flexibles. Donc, en C #, quand devrions-nous utiliser des classes abstraites au lieu d’interfaces avec des méthodes d’extension?

LINQ est un exemple notable de modèle de méthode interface + extension: la fonctionnalité de requête est fournie pour tout type implémenté IEnumerablevia une multitude de méthodes d'extension.


2
Et nous pouvons aussi utiliser 'Propriétés' dans les interfaces.
Gulshan le

1
Cela ressemble à une question spécifique à C # plutôt qu’à une question OOD générale. (La plupart des autres langues OO ne disposent ni de méthodes d'extension ni de propriétés.)
Péter Török le


4
Les méthodes d'extension ne résolvent pas tout à fait le problème que résolvent les classes abstraites. La raison en est donc identique à celle de Java ou d'un autre langage similaire à héritage unique.
Job

3
Le besoin d'implémentations de méthodes de classes abstraites n'a pas été réduit par les méthodes d'extension. Les méthodes d'extension ne peuvent pas accéder aux champs de membres privés, ni être déclarées virtuelles et être remplacées dans des classes dérivées.
zooone9243

Réponses:


63

Lorsque vous avez besoin d'une partie de la classe à implémenter. Le meilleur exemple que j'ai utilisé est le modèle de méthode template .

public abstract class SomethingDoer
{
    public void Do()
    {
        this.DoThis();
        this.DoThat();
    }

    protected abstract void DoThis();
    protected abstract void DoThat();
}

Ainsi, vous pouvez définir les étapes à suivre lors de l'appel de Do (), sans connaître les spécificités de leur implémentation. Les classes dérivées doivent implémenter les méthodes abstraites, mais pas la méthode Do ().

Les méthodes d'extensions ne répondent pas nécessairement à la partie "doivent faire partie de la classe" de l'équation. De plus, les méthodes d'extension iirc ne peuvent (sembler être) rien d'autre que public.

modifier

La question est plus intéressante que ce que je pensais à l’origine. Après un examen plus approfondi, Jon Skeet a répondu à une question comme celle-ci sur SO en faveur de l'utilisation d'interfaces + méthodes d'extension. En outre, un inconvénient potentiel consiste à utiliser la réflexion par rapport à une hiérarchie d'objets conçue de cette manière.

Personnellement, j'ai du mal à voir l'intérêt de modifier une pratique actuellement courante, mais je ne vois également que peu ou pas d'inconvénients à le faire.

Il est à noter qu'il est possible de programmer de cette manière dans de nombreuses langues via les classes utilitaires. Les extensions fournissent simplement le sucre syntaxique pour donner l’apparence des méthodes à la classe.


Battez-moi à elle ..
Giorgi

1
Mais on peut faire la même chose avec les interfaces et les méthodes d'extension. Là, les méthodes que vous définissez dans la classe de base abstraite Do()seront définies comme une méthode d’extension. Et les méthodes laissées pour la définition des classes dérivées DoThis()seront comme membres de l'interface principale. Et avec les interfaces, vous pouvez prendre en charge plusieurs implémentations / héritages. Et voyez ceci
Gulshan le

@Gulshan: Parce qu'une chose peut être faite de plusieurs façons ne rend pas la manière choisie invalide. L'utilisation d'interfaces ne présente aucun avantage. La classe abstraite elle - même est l'interface. Vous n'avez pas besoin d'interfaces pour prendre en charge plusieurs implémentations.
ThomasX

En outre, le modèle interface + extension peut présenter des inconvénients. Prenez la bibliothèque Linq, par exemple. C'est génial, mais si vous ne devez l'utiliser qu'une seule fois dans ce fichier de code, la bibliothèque Linq complète sera disponible dans IntelliSense sur EVERY IEnumerable dans votre fichier de code. Cela peut considérablement ralentir les performances de l'IDE.
KeithS

-1 parce que vous n'avez pas démontré comment les classes abstraites conviennent mieux à la méthode des modèles que les interfaces
Benjamin Hodgson

25

Tout dépend de ce que vous voulez modéliser:

Les classes abstraites fonctionnent par héritage . S'agissant uniquement de classes de base spéciales, ils modélisent une relation.

Par exemple, un chien est un animal, nous avons donc

class Dog : Animal { ... }

Comme il n’a pas de sens de créer un animal générique générique (à quoi cela ressemblerait-il?), Nous fabriquons cette Animalclasse de base abstract- mais c’est toujours une classe de base. Et la Dogclasse n'a pas de sens sans être un Animal.

Les interfaces en revanche sont une autre histoire. Ils n'utilisent pas l'héritage mais fournissent un polymorphisme (qui peut également être implémenté avec l'héritage). Ils ne modélisent pas une relation is-a , mais plus d'une relation prend en charge .

Prenons par exemple IComparableun objet qui peut être comparé à un autre .

La fonctionnalité d'une classe ne dépend pas des interfaces qu'elle implémente, l'interface fournit simplement un moyen générique d'accéder à la fonctionnalité. Nous pourrions encore Disposed'un Graphicsou d'un FileStreamsans les avoir mettre en œuvre IDisposable. L' interface ne fait que relier les méthodes.

En principe, on pourrait ajouter et supprimer des interfaces comme des extensions sans modifier le comportement d'une classe, mais simplement en enrichir l'accès. Vous ne pouvez cependant pas enlever une classe de base car l'objet deviendrait sans signification!


Quand choisiriez-vous de conserver un résumé de classe de base?
Gulshan le

1
@Gulshan: Si c'est le cas - pour une raison quelconque - cela n'a pas de sens de créer réellement une instance de la classe de base. Vous pouvez "créer" un Chihuahua ou un requin blanc, mais vous ne pouvez pas créer un animal sans spécialisation supplémentaire - vous feriez ainsi de l' Animalabstrait. Cela vous permet d'écrire des abstractfonctions à spécialiser par les classes dérivées. Ou plus en termes de programmation - il ne fait probablement pas de sens pour créer un UserControl, mais comme Button est un UserControl, nous avons donc le même genre de relation classe / baseclass.
Dario

si vous avez des méthodes abstraites, vous avez une classe abstraite. Et les méthodes abstraites que vous avez quand il n’ya aucune valeur pour les implémenter (comme "go" pour animal). Et bien sûr, parfois, vous ne voulez pas autoriser les objets d'une classe générale, mais plutôt abstraits.
Dainius

Aucune règle ne dit que s'il est grammaticalement et sémantiquement significatif de dire "B est un A", alors A doit être une classe abstraite. Vous devriez préférer les interfaces jusqu'à ce que vous ne puissiez vraiment pas éviter la classe. Le partage de code peut s'effectuer via la composition d'interfaces, etc. Il est ainsi plus facile d'éviter de se mettre dans un coin avec d'étranges arbres d'héritage.
Sara

3

Je viens de me rendre compte que je n'ai pas utilisé de classes abstraites (du moins pas créées) depuis des années.

La classe abstraite est une bête quelque peu étrange entre l'interface et la mise en œuvre. Il y a quelque chose dans les classes abstraites qui picotent mon sens de l'araignée. Je dirais qu'il ne faut en aucun cas "exposer" la mise en oeuvre derrière l'interface (ou faire des liens).

Même s'il est nécessaire d'avoir un comportement commun entre les classes implémentant une interface, j'utiliserais une classe d'assistance indépendante, plutôt qu'une classe abstraite.

Je voudrais aller pour les interfaces.


2
-1: Je ne suis pas sûr de votre âge, mais vous voudrez peut-être apprendre les modèles de conception, en particulier le modèle de méthode de modèle. Les modèles de conception feront de vous un meilleur programmeur et mettront fin à votre ignorance des classes abstraites.
Jim G.

Idem pour la sensation de picotement. Les classes, la principale fonctionnalité de C # et de Java, permettent de créer un type et de le chaîner immédiatement pour une implémentation unique.
Jesse Millikan le

2

IMHO, je pense qu'il y a un mélange de concepts ici.

Les classes utilisent un certain type d'héritage, tandis que les interfaces utilisent un autre type d'héritage.

Les deux types d'héritage sont importants et utiles, et chacun a ses avantages et ses inconvénients.

Commençons par le début.

L'histoire avec les interfaces commence il y a longtemps avec C ++. Au milieu des années 1980, lorsque le C ++ a été développé, l'idée d'interfaces en tant que types différents n'était pas encore concrétisée (elle viendrait avec le temps).

C ++ n'avait qu'un seul type d'héritage, le type d'héritage utilisé par les classes, appelé héritage d'implémentation.

Pour pouvoir appliquer la recommandation du livre GoF: "Programmer pour l'interface, et non pour l'implémentation", les classes abstraites prises en charge par C ++ et l'héritage de plusieurs applications pour pouvoir faire quelles interfaces en C # sont capables (et utiles!) .

C # introduit un nouveau type de type, des interfaces, et un nouveau type d'héritage, l'héritage d'interface, afin d'offrir au moins deux manières différentes de prendre en charge "Pour programmer pour l'interface, et non pour l'implémentation", avec une mise en garde importante: C # uniquement prend en charge l'héritage multiple avec l'héritage d'interface (et non avec l'héritage d'implémentation).

Ainsi, les raisons architecturales derrière les interfaces en C # sont la recommandation du GoF.

J'espère que cela peut être utile.

Cordialement, Gastón


1

L'application de méthodes d'extension à une interface est utile pour appliquer un comportement commun à des classes pouvant partager uniquement une interface commune. De telles classes peuvent déjà exister et être fermées aux extensions par d'autres moyens.

Pour les nouvelles conceptions, je privilégierais l’utilisation de méthodes d’extension sur les interfaces. Pour une hiérarchie de types, les méthodes d'extension fournissent un moyen d'étendre le comportement sans avoir à ouvrir toute la hiérarchie pour modification.

Cependant, les méthodes d'extension ne peuvent pas accéder à l'implémentation privée, donc au sommet d'une hiérarchie, si j'ai besoin d'encapsuler une implémentation privée derrière une interface publique, une classe abstraite serait alors le seul moyen.


Non, ce n'est pas le seul moyen sûr. Il existe un autre moyen plus sûr. C'est les méthodes d'extension. Les clients ne seront pas infectés du tout. C'est pourquoi j'ai mentionné les interfaces + méthodes d'extension.
Gulshan

@ Ed James, je pense "moins puissant" = "plus flexible". Quand choisiriez-vous une classe abstraite plus puissante au lieu d’une interface plus souple?
Gulshan le

@Ed James Dans asp.mvc, les méthodes d'extension ont été utilisées dès le début. Qu'est ce que tu penses de ça?
Gulshan

0

Je pense qu'en C #, l'héritage multiple n'est pas pris en charge et c'est pourquoi le concept d'interface est introduit en C #.

Dans le cas de classes abstraites, l'héritage multiple n'est pas non plus pris en charge. Il est donc facile de choisir d'utiliser des classes abstraites ou des interfaces.


Pour être précis, ce sont des classes abstraites pures qui sont appelées "interfaces" en C #.
Johan Boulé

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.