Per me, questo ha senso in quanto dà un nome concreto alla classe, invece di fare affidamento sul generico NamedEntity. D'altra parte, esiste un numero di tali classi che semplicemente non hanno proprietà aggiuntive.
Ci sono aspetti negativi di questo approccio?
L'approccio non è male, ma ci sono soluzioni migliori disponibili. In breve, un'interfaccia sarebbe una soluzione molto migliore per questo. Il motivo principale per cui le interfacce e l'ereditarietà sono diverse qui è perché puoi ereditare solo da una classe, ma puoi implementare molte interfacce .
Ad esempio, considera di avere entità nominate e entità controllate. Hai diverse entità:
One
non è un'entità controllata né un'entità denominata. È semplice:
public class One
{ }
Two
è un'entità denominata ma non un'entità controllata. Questo è essenzialmente quello che hai ora:
public class NamedEntity
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Two : NamedEntity
{ }
Three
è sia una voce denominata che controllata. Qui è dove ti imbatti in un problema. Puoi creare una AuditedEntity
classe base, ma non puoi Three
ereditare entrambi AuditedEntity
e NamedEntity
:
public class AuditedEntity
{
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
}
public class Three : NamedEntity, AuditedEntity // <-- Compiler error!
{ }
Tuttavia, potresti pensare a una soluzione alternativa AuditedEntity
ereditando da NamedEntity
. Questo è un trucco intelligente per garantire che ogni classe abbia solo bisogno di ereditare (direttamente) da un'altra classe.
public class AuditedEntity : NamedEntity
{
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
}
public class Three : AuditedEntity
{ }
Funziona ancora. Ma ciò che hai fatto qui è dichiarato che ogni entità controllata è intrinsecamente anche un'entità denominata . Il che mi porta al mio ultimo esempio. Four
è un'entità controllata ma non un'entità denominata. Ma non puoi lasciarti Four
ereditare da AuditedEntity
come lo faresti anche a NamedEntity
causa dell'eredità tra AuditedEntity and
NamedEntity`.
Usando l'ereditarietà, non c'è modo di fare entrambe le cose Three
e Four
funzionare a meno che non si inizi a duplicare le classi (il che apre una nuova serie di problemi).
Utilizzando le interfacce, questo può essere facilmente raggiunto:
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; }
}
L'unico inconveniente minore qui è che devi ancora implementare l'interfaccia. Ma ottieni tutti i vantaggi dall'avere un tipo riutilizzabile comune, senza nessuno degli svantaggi che emergono quando hai bisogno di variazioni su più tipi comuni per una data entità.
Ma il tuo polimorfismo rimane intatto:
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'altra parte, esiste un numero di tali classi che semplicemente non hanno proprietà aggiuntive.
Questa è una variante del modello di interfaccia marcatore , in cui si implementa un'interfaccia vuota puramente per poter utilizzare il tipo di interfaccia per verificare se una determinata classe è "contrassegnata" con questa interfaccia.
Stai usando classi ereditate invece di interfacce implementate, ma l'obiettivo è lo stesso, quindi mi riferirò ad esso come una "classe contrassegnata".
A prima vista, non c'è niente di sbagliato nelle interfacce / classi dei marker. Sono sintatticamente e tecnicamente validi e non ci sono inconvenienti intrinseci nell'utilizzarli, a condizione che il marker sia universalmente vero (al momento della compilazione) e non condizionale .
Questo è esattamente il modo in cui dovresti distinguere tra diverse eccezioni, anche quando tali eccezioni non hanno effettivamente proprietà / metodi aggiuntivi rispetto al metodo di base.
Quindi non c'è nulla di intrinsecamente sbagliato nel farlo, ma consiglierei di usarlo con cautela, assicurandomi che non stai solo cercando di nascondere un errore architettonico esistente con un polimorfismo mal progettato.
OrderDateInfo
s da quelli che sono rilevanti per altriNamedEntity
s