Si nous supposons qu'il n'est pas souhaitable que la classe de base soit une classe d'interface pure, et en utilisant les 2 exemples ci-dessous, quelle est la meilleure approche, en utilisant la définition de classe de méthode abstraite ou virtuelle?
L'avantage de la version "abstraite" est qu'elle est probablement plus propre et force la classe dérivée à donner une implémentation, espérons-le, significative.
L'avantage de la version "virtuelle" est qu'elle peut être facilement tirée par d'autres modules et utilisée pour les tests sans ajouter un tas de framework sous-jacent comme la version abstraite l'exige.
Version abstraite:
public abstract class AbstractVersion
{
public abstract ReturnType Method1();
public abstract ReturnType Method2();
.
.
public abstract ReturnType MethodN();
//////////////////////////////////////////////
// Other class implementation stuff is here
//////////////////////////////////////////////
}
Version virtuelle:
public class VirtualVersion
{
public virtual ReturnType Method1()
{
return ReturnType.NotImplemented;
}
public virtual ReturnType Method2()
{
return ReturnType.NotImplemented;
}
.
.
public virtual ReturnType MethodN()
{
return ReturnType.NotImplemented;
}
//////////////////////////////////////////////
// Other class implementation stuff is here
//////////////////////////////////////////////
}
return ReturnType.NotImplemented
? Sérieusement? Si vous ne pouvez pas rejeter le type non implémenté au moment de la compilation (vous pouvez; utiliser des méthodes abstraites) au moins lever une exception.Réponses:
Mon vote, si je consommais vos affaires, serait pour les méthodes abstraites. Cela va de pair avec «échouer tôt». Il peut être difficile au moment de la déclaration d'ajouter toutes les méthodes (bien que tout outil de refactorisation décent le fasse rapidement), mais au moins je sais quel est le problème immédiatement et le corrige. Je préfère cela plutôt que de déboguer 6 mois et 12 personnes de changements plus tard pour voir pourquoi nous obtenons soudainement une exception non implémentée.
la source
La version virtuelle est à la fois sujette aux bogues et sémantiquement incorrecte.
Le résumé dit "cette méthode n'est pas implémentée ici. Vous devez l'implémenter pour que cette classe fonctionne"
Virtual dit "J'ai une implémentation par défaut mais vous pouvez me changer si vous en avez besoin"
Si votre objectif ultime est la testabilité, les interfaces sont normalement la meilleure option. (cette classe fait x plutôt que cette classe est ax). Vous devrez peut-être diviser vos classes en composants plus petits pour que cela fonctionne correctement.
la source
Cela dépend de l'utilisation de votre classe.
Si les méthodes ont une implémentation «vide» raisonnable, vous avez beaucoup de méthodes et vous en remplacez souvent quelques-unes, alors utiliser des
virtual
méthodes est logique. Par exemple,ExpressionVisitor
est implémenté de cette façon.Sinon, je pense que vous devriez utiliser des
abstract
méthodes.Idéalement, vous ne devriez pas avoir de méthodes qui ne sont pas implémentées, mais dans certains cas, c'est la meilleure approche. Mais si vous décidez de le faire, ces méthodes devraient lancer
NotImplementedException
, et non retourner une valeur spéciale.la source
Je vous suggère de reconsidérer la définition d'une interface distincte que votre classe de base implémente, puis de suivre l'approche abstraite.
Imagerie de code comme ceci:
Faire cela résout ces problèmes:
En ayant tout le code qui utilise des objets dérivés de AbstractVersion peut maintenant être implémenté pour recevoir l'interface IVersion à la place, cela signifie qu'ils peuvent être plus facilement testés unitairement.
La version 2 de votre produit peut alors implémenter une interface IVersion2 pour fournir des fonctionnalités supplémentaires sans casser votre code client existant.
par exemple.
Il vaut également la peine de lire sur l'inversion des dépendances, pour empêcher cette classe de contenir des dépendances codées en dur qui empêchent les tests unitaires efficaces.
la source
L'injection de dépendances repose sur des interfaces. Voici un court exemple. L'élève de classe a une fonction appelée CreateStudent qui nécessite un paramètre qui implémente l'interface "IReporting" (avec une méthode ReportAction). Après avoir créé un étudiant, il appelle ReportAction sur le paramètre de classe concret. Si le système est configuré pour envoyer un e-mail après avoir créé un étudiant, nous envoyons une classe concrète qui envoie un e-mail dans son implémentation ReportAction, ou nous pouvons envoyer une autre classe concrète qui envoie une sortie à une imprimante dans son implémentation ReportAction. Idéal pour la réutilisation du code.
la source