Für mich ist dies sinnvoll, da es der Klasse einen konkreten Namen gibt, anstatt sich auf die generische NamedEntity zu verlassen. Andererseits gibt es eine Reihe solcher Klassen, die einfach keine zusätzlichen Eigenschaften haben.
Gibt es Nachteile bei diesem Ansatz?
Der Ansatz ist nicht schlecht, aber es gibt bessere Lösungen. Kurz gesagt, eine Schnittstelle wäre dafür eine viel bessere Lösung. Der Hauptgrund , warum Schnittstellen und Vererbung hier anders ist , weil man nur von einer Klasse erben kann, aber man kann viele Schnittstellen implementieren .
Angenommen, Sie haben Entitäten und geprüfte Entitäten benannt. Sie haben mehrere Entitäten:
One
ist weder eine geprüfte noch eine benannte Stelle. Das ist einfach:
public class One
{ }
Two
ist eine benannte Stelle, aber keine geprüfte Stelle. Das ist im Wesentlichen, was Sie jetzt haben:
public class NamedEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Two : NamedEntity
{ }
Three
ist sowohl ein benannter als auch ein geprüfter Eintrag. Hier stößt man auf ein Problem. Sie können eine AuditedEntity
Basisklasse erstellen , aber nicht beideThree
erben und : AuditedEntity
NamedEntity
public class AuditedEntity
{
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
}
public class Three : NamedEntity, AuditedEntity // <-- Compiler error!
{ }
Sie könnten jedoch auch von einer Vermeidung des Problems denken , indem AuditedEntity
vererben NamedEntity
. Dies ist ein cleverer Hack, um sicherzustellen, dass jede Klasse nur (direkt) von einer anderen Klasse erben muss.
public class AuditedEntity : NamedEntity
{
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
}
public class Three : AuditedEntity
{ }
Das funktioniert immer noch. Aber was Sie hier getan haben, ist, dass jede geprüfte Entität von Natur aus auch eine benannte Entität ist . Das bringt mich zu meinem letzten Beispiel. Four
ist eine geprüfte Entität, aber keine benannte Entität. Aber Sie können nicht von Four
erben lassen, AuditedEntity
da Sie es dann auch NamedEntity
aufgrund der Vererbung zwischen AuditedEntity and
NamedEntity` machen würden.
Wenn Sie die Vererbung verwenden, gibt es keine Möglichkeit, beides zu erreichen Three
und zu Four
funktionieren, es sei denn, Sie beginnen mit dem Duplizieren von Klassen (was eine ganze Reihe neuer Probleme aufwirft).
Mit Hilfe von Schnittstellen kann dies leicht erreicht werden:
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; }
}
Der einzige kleine Nachteil hierbei ist, dass Sie die Schnittstelle noch implementieren müssen. Sie profitieren jedoch von allen Vorteilen eines gemeinsamen wiederverwendbaren Typs, ohne die Nachteile zu haben, die sich ergeben, wenn Sie Variationen mehrerer gemeinsamer Typen für eine bestimmte Entität benötigen.
Ihr Polymorphismus bleibt jedoch erhalten:
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);
Andererseits gibt es eine Reihe solcher Klassen, die einfach keine zusätzlichen Eigenschaften haben.
Dies ist eine Variation des Markierungsschnittstellenmusters , bei dem Sie eine leere Schnittstelle implementieren, um anhand des Schnittstellentyps zu überprüfen, ob eine bestimmte Klasse mit dieser Schnittstelle "markiert" ist.
Sie verwenden geerbte Klassen anstelle von implementierten Schnittstellen, aber das Ziel ist das gleiche, daher werde ich es als "markierte Klasse" bezeichnen.
Mit dem Nennwert ist nichts falsch an Marker-Interfaces / -Klassen. Sie sind syntaktisch und technisch gültig und haben keine inhärenten Nachteile, vorausgesetzt, der Marker ist universell gültig (zur Kompilierungszeit) und nicht bedingt .
Genau so sollten Sie zwischen verschiedenen Ausnahmen unterscheiden, auch wenn diese Ausnahmen im Vergleich zur Basismethode keine zusätzlichen Eigenschaften / Methoden aufweisen.
Es ist also von Natur aus nichts Falsches daran, aber ich würde raten, dies vorsichtig zu verwenden, um sicherzustellen, dass Sie nicht nur versuchen, einen vorhandenen Architekturfehler mit schlecht entworfenem Polymorphismus zu vertuschen.
OrderDateInfo
s relevant sind, von denen unterscheiden, die für andereNamedEntity
s relevant sind