Personnellement, je n'ai presque jamais besoin d'écrire des classes abstraites.
La plupart du temps, je vois des classes abstraites (mal) utilisées, c'est parce que l'auteur de la classe abstraite utilise le modèle de la "méthode modèle".
Le problème avec la "méthode modèle" est qu'elle est presque toujours quelque peu ré-entrante - la classe "dérivée" connaît non seulement la méthode "abstraite" de sa classe de base qu'elle implémente, mais aussi les méthodes publiques de la classe de base, même si la plupart du temps elle n'a pas besoin de les appeler.
Exemple (excessivement simplifié) :
abstract class QuickSorter
{
public void Sort(object[] items)
{
// implementation code that somewhere along the way calls:
bool less = compare(x,y);
// ... more implementation code
}
abstract bool compare(object lhs, object rhs);
}
Ainsi, ici, l'auteur de cette classe a écrit un algorithme générique et souhaite que les gens l'utilisent en le "spécialisant" en fournissant leurs propres "crochets" - dans ce cas, une méthode "compare".
L'utilisation prévue est donc quelque chose comme ceci :
class NameSorter : QuickSorter
{
public bool compare(object lhs, object rhs)
{
// etc.
}
}
Le problème, c'est que vous avez indûment associé deux concepts :
- Une façon de comparer deux éléments (quel élément doit passer en premier)
- Une méthode de tri des éléments (c'est-à-dire tri rapide ou tri par fusion, etc.).
Dans le code ci-dessus, théoriquement, l'auteur de la méthode "compare" peut de manière rentrante dans la méthode "Sort" de la superclasse... même si, dans la pratique, ils n'auront jamais envie ou besoin de le faire.
Le prix à payer pour ce couplage inutile est qu'il est difficile de changer la superclasse et, dans la plupart des langages OO, impossible de la changer au moment de l'exécution.
L'autre méthode consiste à utiliser le modèle de conception "Stratégie" à la place :
interface IComparator
{
bool compare(object lhs, object rhs);
}
class QuickSorter
{
private readonly IComparator comparator;
public QuickSorter(IComparator comparator)
{
this.comparator = comparator;
}
public void Sort(object[] items)
{
// usual code but call comparator.Compare();
}
}
class NameComparator : IComparator
{
bool compare(object lhs, object rhs)
{
// same code as before;
}
}
Alors remarquez maintenant : Tout ce que nous avons, ce sont des interfaces, et des implémentations concrètes de ces interfaces. En pratique, vous n'avez pas vraiment besoin d'autre chose pour faire une conception OO de haut niveau.
Pour "cacher" le fait que nous avons implémenté le "tri des noms" en utilisant une classe "QuickSort" et un "NameComparator", nous pouvons toujours écrire une méthode de fabrique quelque part :
ISorter CreateNameSorter()
{
return new QuickSorter(new NameComparator());
}
Tout Dès que vous avez une classe abstraite, vous pouvez le faire... même lorsqu'il existe une relation ré-entrante naturelle entre la classe de base et la classe dérivée, il est généralement utile de les rendre explicites.
Une dernière pensée : Tout ce que nous avons fait ci-dessus est de "composer" une fonction "NameSorting" en utilisant une fonction "QuickSort" et une fonction "NameComparison"... dans un langage de programmation fonctionnel, ce style de programmation devient encore plus naturel, avec moins de code.
1 votes
Cette question a été posée à plusieurs reprises : stackoverflow.com/questions/56867/interface-vs-base-class
1 votes
En plus des réponses ci-dessous, voici une courte liste de cas où vous pouvez préférer les interfaces et où vous ne pouvez pas : When To Use Interfaces : msdn.microsoft.com/fr/us/library/3b5b8ezk(v=vs.80).aspx
0 votes
Utilisez abstract quand vous n'êtes pas sûr de ce que la classe va faire. utilisez interface si vous l'êtes.
1 votes
Je suis curieux de voir combien de développeurs, qui ne travaillent pas chez Microsoft, définissent et utilisent des interfaces dans leur développement quotidien.