Pour moi, cela a du sens car cela donne un nom concret à la classe, au lieu de compter sur le nom générique NamedEntity. D'autre part, un certain nombre de ces classes n'ont tout simplement pas de propriétés supplémentaires.
Y a-t-il des inconvénients à cette approche?
L’approche n’est pas mauvaise, mais de meilleures solutions sont disponibles. En bref, une interface serait une bien meilleure solution pour cela. La principale raison pour laquelle les interfaces et l'héritage sont différents ici est que vous ne pouvez hériter que d'une classe, mais que vous pouvez implémenter de nombreuses interfaces .
Par exemple, considérons que vous avez nommé des entités et des entités auditées. Vous avez plusieurs entités:
One
n'est pas une entité auditée ni une entité nommée. C'est simple:
public class One
{ }
Two
est une entité nommée mais pas une entité auditée. C'est essentiellement ce que vous avez maintenant:
public class NamedEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Two : NamedEntity
{ }
Three
est à la fois une entrée nommée et auditée. C'est là que vous rencontrez un problème. Vous pouvez créer une AuditedEntity
classe de base, mais vous ne pouvez pas faire Three
hériter les deux AuditedEntity
et NamedEntity
:
public class AuditedEntity
{
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
}
public class Three : NamedEntity, AuditedEntity // <-- Compiler error!
{ }
Cependant, vous pourriez penser à une solution de contournement en AuditedEntity
héritant de NamedEntity
. C'est un astuce astucieuse pour s'assurer que chaque classe n'a besoin que d'hériter (directement) d'une autre classe.
public class AuditedEntity : NamedEntity
{
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
}
public class Three : AuditedEntity
{ }
Cela fonctionne toujours. Mais ce que vous avez fait ici indique que chaque entité auditée est également une entité nommée . Ce qui m'amène à mon dernier exemple. Four
est une entité auditée mais pas une entité nommée. Mais vous ne pouvez pas laisser Four
hériter de AuditedEntity
comme vous le feriez également en NamedEntity
raison de l'héritage entre AuditedEntity and
NamedEntity`.
En utilisant l'héritage, il n'y a aucun moyen de faire les deux Three
et de Four
travailler si vous ne commencez pas à dupliquer les classes (ce qui crée un nouvel ensemble de problèmes).
En utilisant des interfaces, ceci peut être facilement réalisé:
public interface INamedEntity
{
int Id { get; set; }
string Name { get; set; }
}
public interface IAuditedEntity
{
DateTime CreatedOn { get; set; }
DateTime UpdatedOn { get; set; }
}
public class One
{ }
public class Two : INamedEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Three : INamedEntity, IAuditedEntity
{
public int Id { get; set; }
public string Name { get; set; }
DateTime CreatedOn { get; set; }
DateTime UpdatedOn { get; set; }
}
public class Four : IAuditedEntity
{
DateTime CreatedOn { get; set; }
DateTime UpdatedOn { get; set; }
}
Le seul inconvénient mineur ici est que vous devez toujours implémenter l'interface. Mais vous bénéficiez de tous les avantages d'un type commun réutilisable, sans aucun des inconvénients qui apparaissent lorsque vous avez besoin de variations sur plusieurs types communs pour une entité donnée.
Mais votre polymorphisme reste intact:
var one = new One();
var two = new Two();
var three = new Three();
var four = new Four();
public void HandleNamedEntity(INamedEntity namedEntity) {}
public void HandleAuditedEntity(IAuditedEntity auditedEntity) {}
HandleNamedEntity(one); //Error - not a named entity
HandleNamedEntity(two);
HandleNamedEntity(three);
HandleNamedEntity(four); //Error - not a named entity
HandleAuditedEntity(one); //Error - not an audited entity
HandleAuditedEntity(two); //Error - not an audited entity
HandleAuditedEntity(three);
HandleAuditedEntity(four);
D'autre part, un certain nombre de ces classes n'ont tout simplement pas de propriétés supplémentaires.
Il s'agit d'une variante du modèle d'interface de marqueur , dans lequel vous implémentez une interface vide uniquement pour pouvoir utiliser le type d'interface afin de vérifier si une classe donnée est "marquée" avec cette interface.
Vous utilisez des classes héritées au lieu d'interfaces implémentées, mais l'objectif est le même. Je vais donc parler d'une "classe marquée".
À première vue, il n'y a rien de mal avec les interfaces / classes de marqueurs. Ils sont valides du point de vue syntaxique et technique, et leur utilisation ne présente aucun inconvénient, à condition que le marqueur soit universellement vrai (au moment de la compilation) et non conditionnel .
C’est exactement la manière dont vous devriez différencier les différentes exceptions, même si ces exceptions n’ont pas de propriétés / méthodes supplémentaires par rapport à la méthode de base.
Il n’ya donc rien de mal à cela, mais je vous conseillerais de ne pas dissimuler une erreur architecturale existante avec un polymorphisme mal conçu.
OrderDateInfo
s de celles qui s'appliquent aux autresNamedEntity
s