ORM Ereditarietà mappatura o no, come mappare proprietà condivise e specifiche per un insieme di oggetti del mondo reale correlati

1

Quando stavo iniziando a programmare OO, principalmente Symfony e Doctrine ORM, mi è stato consigliato almeno una volta di applicare il mapping dell'eredità per un caso come segue:

Ad esempio, un sistema di gestione delle risorse umane che si occupa di candidati di lavoro, impiegati, ex dipendenti, impiegati a tempo parziale ecc.

Ci sarebbe un utente di classe entità, e poiché ci sono altri tipi di utenti, come sopra menzionato, ci sarebbero alcune altre classi figlio, ad es. Dipendente, ecc.

La differenza tra gli utenti è che ogni tipo ha un insieme di proprietà specifiche. E tutti condividono un insieme di proprietà.

Quindi, le proprietà condivise (ad esempio ID, Nome, Cognome) sarebbero nella classe genitore Utente e il resto, quelle specifiche sarebbero in particolare classe figlio.

Guardando Doctrine docs, un modo pulito per mappare questo, credo, sarebbe il Ereditarietà di tabelle singole

Non sto dando esempi di comportamenti per le classi, a questo punto, ed è apposta. Il caso forse implica inevitabili differenti implementazioni di un certo comportamento, che giustificherebbe il polimorfismo.

Vorrei essere sicuro su come gestire prima determinate proprietà.

Sei d'accordo sul fatto che la mappatura dell'eredità sia giustificata in questo caso?

La presenza di proprietà condivise e specifiche per un gruppo di oggetti del mondo reale implica inevitabilmente il polimorfismo?

    
posta Đuro Mandinić 26.05.2017 - 10:32
fonte

1 risposta

3

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à.

    
risposta data 27.05.2017 - 00:37
fonte

Leggi altre domande sui tag