Je ne pouvais pas trouver une question qui n'était pas trop spécifique à certains cas, je vais donc essayer de rendre cela très générique.
Nous avons besoin d'une classe de base d'extraction dans un ensemble de documents, par exemple. Chaque document a ses propriétés spécifiques, mais ils sont finalement documentaires. Nous voulons donc fournir des opérations d'extraction communes pour toutes.
Même s'ils sont tous des documents, comme je l'ai dit, ils sont quelque peu différents. Certains peuvent avoir des propriétés, mais certains peuvent ne pas.
Imaginons que nous avons la classe abstraite Document
de base et les classes FancyDocument
et NotSoFancyDocument
classes qui héritent de celle-ci. Le FancyDocument
a un SectionA
, le NotSoFancyDocument
ne le fait pas.
Cela dit, que voudriez-vous défendre comme la meilleure façon de la mettre en œuvre? Voici les deux options:
Les méthodes virtuelles vides sur la classe de base permettraient au programmeur de remplacer uniquement les méthodes qui ont du sens pour les différents types de documents. Nous aurions alors un comportement par défaut sur la classe de base abstraite, qui retournerait le default
pour les méthodes, comme celle-ci:
public abstract class Document
{
public virtual SectionA GetDocumentSectionA()
{
return default(SectionA);
}
}
public class FancyDocument : Document
{
public override SectionA GetDocumentSectionA()
{
// Specific implementation
}
}
public class NotSoFancyDocument : Document
{
// Does not implement method GetDocumentSectionA because it doesn't have a SectionA
}
NotImplementedException
Depuis le NotSoFancyDocument
ne fait pas avoir un SectionA
, mais les autres le font, nous pourrions simplement retourner le default Pour la méthode dedans, ou nous pourrions lancer un NotImplementedException
. Cela dépendrait de la manière dont le programme a été écrit et d'autres choses. Nous pourrions trouver quelque chose comme ça:
//// Return the default value
public abstract class Document
{
public abstract SectionA GetDocumentSectionA();
}
public class FancyDocument : Document
{
public override SectionA GetDocumentSectionA()
{
// Specific implementation
}
}
public class NotSoFancyDocument : Document
{
public override SectionA GetDocumentSectionA()
{
return default(SectionA);
}
}
OR
//// Throw an exception
public abstract class Document
{
public abstract SectionA GetDocumentSectionA();
}
public class FancyDocument : Document
{
public override SectionA GetDocumentSectionA()
{
// Specific implementation
}
}
public class NotSoFancyDocument : Document
{
public override SectionA GetDocumentSectionA()
{
throw new NotImplementedException("NotSoFancyDocument does not have a section A");
}
}
Personnellement, je pense que l'approche de la méthode abstraite est meilleure, car cela signifie "hé, j'ai besoin que vous puissiez obtenir une section pour être un document. Je me fiche de la façon." Alors que la méthode virtuelle signifie "Hé, j'ai cette sectiona ici. Si ce n'est pas assez bon pour vous, n'hésitez pas à changer la façon dont je l'obtiens.".
Je pense que le premier est un signe d'odeur de programmation orientée objet.
Quelles sont vos opinions a ce sujet?
Dans ce cas, la classe de base ne devrait rien savoir de la sectiona. La classe dérivée doit mettre en œuvre les propriétés supplémentaires dont les besoins de type.
Pour certaines opérations dans lesquelles une autre classe doit extraire des informations, quel que soit le type de document, vous souhaiterez cette méthode sur la classe de base idéalement virtuelle avec une implémentation de base et permettre aux classes dérivées de le remplacer si nécessaire (par exemple, ToPlainText
qui Il suffirait de générer toutes les sections d'un document serait sur Document
, FancyDocument
peut remplacer la mise en œuvre afin de produire également SectionA
).
Pour les cas où une autre classe ne se soucie pas du type de document, mais si elle dispose de certaines propriétés, utilisez des interfaces. IDocument
_ aurait toutes les sections communes et Document
le mettrait en œuvre. IDocumentWithSectionA
serait l'inhumation IDocument
et FancyDocument
ierait cela. Cela vous permet ensuite de dériver une autre NeitherFancyNorNotFancyDocument
qui a une sectiona qui peut également implémenter IDocumentsWithSectionA
.
Évidemment, vous auriez des noms plus utiles que IDocumentWithSectionA
mais cela dépend de l'Usecase.
TL; DR Utilisez la classe Abstract pour ce qui devrait être commun à tous les documents et certaines fonctionnalités courantes, utilisez des interfaces comme contrat disant ce qu'un document a.
Comme indiqué dans les commentaires, aucune de ces options n'est très bonne. C'est probablement la raison pour laquelle vous posez également cette question à cette question.
La solution avec méthodes virtuelles vides ne donne aucune information sur ce qui est pris en charge par une instance de document spécifique. En outre, cela vous oblige à ajouter toutes les méthodes possibles dans la classe de base. Cela force tous les clients à être mis à jour en tant que quiconque, lorsque vous décidez que la classe de base a besoin de méthodes supplémentaires.
La solution qui jette des exceptions est peut-être encore pire, car les clients des classes de document devront gérer une exception possible pour chaque méthode. Cela conduit à redireful et à un code peu fiable.
Le problème que vous avez du mal, c'est que les classes de documents ne peuvent pas prédire le résultat souhaité. C'est la logique d'application et qui ne doit pas être mise en œuvre dans la couche de modèle. Au moins pas d'une manière qui conduit à une couche de modèle réutilisable.
Je suggérerais d'utiliser des interfaces pour décrire les ensembles de capacités dont vous avez besoin. Instance de FO, une interface IDocument
, une interface IFancy
et une interface INotSoFancy
. Ces interfaces peuvent ou non être dérivées de IDocument
ou une autre interface commune, car vous parlez de capabibilités, pas d'objets.
Utilisation de cette solution basée sur l'interface, le code client peut déterminer si l'instance de document donnée prend en charge le capabillity souhaité (en essayant de lancer l'instance de document à l'interface souhaitée). Le code client peut agir en conséquence.