Vorrei raccomandare di evitare quell'eredità qui, specialmente quando si parla di lasciare che l'ereditarietà si riversi nel database, a meno che non sia un database orientato agli oggetti.
Ma un tipico RDBMS non è orientato agli oggetti, e chiunque debba scrivere query su una struttura di tabella progettata per supportare l'ereditarietà per il tuo ORM non ti ringrazierà per questo:)
Consiglierei Composition over Ereditarietà praticamente in ogni caso come questo, ed ecco perché.
La parte chiave qui è questa affermazione
And they all share a set of properties.
Questo tende a implicare che questi set di proprietà dovrebbero essere implementati usando l'ereditarietà da qualche tipo di classe base comune, ovvero un Employee
è-a Person
, a Candidate
è-a Person
e così via.
per es.
public class Person
{
public string Name {get;set;}
}
public class Employee:Person
{
public string EmployeeId {get;set;
}
public class Candidate:Person
{
public string CandidateId {get;set;
}
Ma è meglio affrontarlo dal punto di vista del Dominio stesso.
Modella gli Oggetti Dominio per essere estremamente specifici
Sebbene al valore nominale potrebbe sembrare che questo sia il modo migliore e che queste proprietà siano davvero comuni a tutti i tipi di persone, ne sei proprio sicuro?
Certamente tutti i Dipendenti hanno cose come "Nome" come i Candidati, ma chi ha bisogno di sapere che le informazioni dipende realmente da chi sta chiedendo quando stiamo parlando di modellare un Dominio con Programmazione orientata agli oggetti.
For example, the Payroll
system will need to know my name in order to print it on my Payslip; so it makes sense for the SalariedEmployee
to have a "Name" property. However the ClockInOut
module really couldn't care less; its job is just to record the in and out times for an EmployeeId
so to it I am just a TimeClockedEmployee
with a unique Id
.
Se abbiamo ereditato da una comune classe Person, hai queste informazioni trasportate ovunque vuoi se ne hai bisogno.
Ora Name
non è un grosso problema, ma cose come religione, ecc. Questo tipo di dati dovrebbe essere accessibile solo ai moduli che ne hanno davvero bisogno per diversi motivi
- Capacità di mantenere un fuoco strong e piccolo sul dominio che viene modellato; avere proprietà comuni - o peggio, comportamenti - che non saranno mai usati da uno specifico modello di dominio può rendere più difficile lo sviluppo e la manutenzione.
- Capacità di modellare l'area specifica del dominio per modificarla senza dover influire su nessun altro modello nel sistema;
- Sicurezza dei dati, evitando di esporre i dati a parti del sistema che non ne hanno bisogno
Gestione delle modifiche future - esempio
I processi di business nuovi e non ancora scoperti potrebbero richiedere più informazioni che non sono affatto adatte per una classe base, ma sono richieste da più di un% oggetto di tipo% di carattere. per esempio. Diciamo che la nuova legislazione dice che hai bisogno del numero di previdenza sociale sia per Employee
che per Hourly
dipendenti, ma non ne hai bisogno per Salaried
(che nonostante ciò che alcuni potrebbero dire, sono ancora ContractEmployees
:))
So now you have to break your nice inheritance to put duplicate properties on both HourlyEmployee
and SalariedEmployee
anyway or else deal with the possibility for some of your code to deal with objects that can have a null
SSN. Now things are getting messy - does a null
SSN represent a Contractor
, or a mapping error? Who knows...
Approccio migliore
Nel lungo periodo, è più flessibile modellare questo utilizzando la composizione per cui diciamo un impiegato di Hourly ha-a People
, un candidato ha-a Person
e così via.
Quindi un Person
, HourlyEmployee
, SalariedEmployee
, ContractEmployee
, PotentialEmployee
, VisitorNonEmployee
ecc. possono avere (o meno) una struttura PizzaDeliveryPerson
se necessario, che è effettivamente comune a tutti quelli che ce l'hanno, ma che non fanno parte di alcuna gerarchia.
In terms of how this maps to the Database via the ORM, the individual backing tables will have columns for the PersonalInformation
object, and most ORM systems will support Value Object Mapping to allow you to map that subset of properties into the PersonalInformation
object as a Value object on the Entity in question. YMMV depending on the ORM in question.
es.
Questo potrebbe memorizzare informazioni personali su una persona.
public class PersonalInformation
{
public string Name {get;set;}
}
Se hai davvero bisogno di passare oggetti polimorfici, allora le interfacce sono utili qui come le classi base; infatti, poiché puoi implementare più interfacce, direi che sono più utili.
public interface IEmployee
{
string EmployeeId {get;}
}
Ciò potrebbe indicare che un'entità detiene alcune informazioni personali:
public interface IPersonalInformationHolder
{
PersonalInformation PersonalInformation {get;}
}
come visto in
public class Employee : IEmployee,IPersonalInformationHolder
{
public string EmployeeId {get;}
public PersonalInformation PersonalInformation {get;}
}
Nota come PersonalInformation
non ha alcuna informazione personale; perché non lo chiederemo mai per il tuo nome. Ciò sarà fatto da qualche altra parte del sistema, ad es. il modulo di segnalazione. E può ancora essere passato in giro come ClockTimedEmployee
a qualsiasi parte del sistema a cui interessa IEmployee
public class ClockTimedEmployee: IEmployee
{
public string EmployeeId {get;set;}
}
Un altro netto vantaggio di questo approccio è che garantisce che non puoi mai passare accidentalmente un EmployeeId
a qualsiasi metodo che si aspetta un ClockTimedEmployee
; il codice semplicemente non verrà compilato. Questo aiuta a tagliare un'intera classe di bug di runtime che sono difficili da evitare con l'ereditarietà.