Il modo più semplice per espandere una classe base senza mappare esplicitamente le proprietà in C #

4

Supponiamo che abbia la seguente classe base:

public class Base 
{
    public int Id {get; set;}
    public string SomeText {get; set;}
    public string SomeOtherText {get; set;}

    public static Base BuildFromOtherAssembly(ClassFromAssemblyA source)
    {
        Id = source.Id;
        SomeText = source.SomeText;
        SomeOtherText = source.SomeOtherText
    }
}

public class Derived : Base 
{
    public DataFromAssemblyB DataFromAssemblyB {get; set;}

    public Derived(DataFromAssemblyB dataFromAssemblyB)
    {
        DataFromAssemblyB = dataFromAssemblyB;
    }
}

La classe base viene creata e restituita da un assembly che fa riferimento a AssemblyA, a AssemblyB, che non conosce nulla su AssemblyA.

AssemblyB ora vuole aggiungere i propri dati alla classe Base e vuole assicurarsi che il tipo restituito indichi che i dati aggiuntivi sono presenti.

Fondamentalmente, voglio assicurarmi che a livello di compilatore sia presente DataFromAssemblyB.

So che la composizione funzionerebbe bene in questo scenario, ma il problema è che Logicamente DataFromAssemblyB appartiene a Base.

Per dare un esempio più realistico, immagina questo scenario:

public class ShoppingCart 
{
    public int Id {get; set;}
    public string OwnerName {get; set;}
    public decimal Value {get; set;}

    public static Base BuildFromDataModel(ShoppingCartDatabaseModel source)
    {
        Id = source.Id;
        OwnerName = source.OwnerName;
        Value = source.Value
    }
}

public class ShoppingCartWithContents : ShoppingCart 
{
    public ShoppingCartContent Content {get; set;}

    public ShoppingCartWithContents (ShoppingCartContent content)
    {
        Content = content;
    }
}

Spero che questo lo spieghi. La ragione per cui ho questo problema è che ShoppingCart proviene dal livello del database, mentre ShoppingCartContent proviene dal livello di memorizzazione nella cache e un livello intermedio unisce i dati.

Voglio una digitazione strong, quindi è sempre chiaro allo sviluppatore se hanno a che fare con una ShoppingCart con contenuti caricati o una ShoppingCart che non lo fa.

Voglio evitare di mappare tutte le proprietà in giro a mano, in quanto ciò potrebbe introdurre bug nel caso uno sviluppatore decidesse di aggiungere una proprietà a ShoppingCart, ma dimentica di mappare quella proprietà in ShoppingCartWithContents.

Per favore fammi sapere quali sono le migliori pratiche in una situazione del genere.

Aggiorna :

Ho creato alcune varianti diverse che ho trovato su Github, mi piacerebbe ricevere feedback e pensieri.

Composizione? Eredità? Gist

La premessa è la stessa: creiamo un oggetto carrello acquisti da un modello di database fittizio, quindi proviamo ad aggiungere dati di contenuto ad esso.

Per testare varie funzionalità, ho pensato che sarebbe bello se il ShoppingCart avesse un metodo .AddContents(content) che permettesse di creare in modo pulito un ShoppingCartWithContents .

Come test di funzionalità aggiuntivo come supporto per Business Logic, ho aggiunto un metodo GetFriendlyString che dovrebbe essere accessibile una volta aggiunti gli articoli.
In alcuni campioni ho eseguito il overdring di GetFriendlyString per avere un extra "With Items" per mostrare che la funzionalità di base può essere estesa.

Ho cercato di riassumere i miei sentimenti sui vari approcci al vertice.

Aggiornamento 2:

Volevo dare un po 'più di informazioni sulle circostanze, in quanto ciò potrebbe influenzare il modo migliore.

Questo è un progetto web api, con EF come ORM. I miei oggetti business come ShoppingCart sono mappati a mano dai modelli Db di EF per selezionare solo le proprietà rilevanti per l'attività commerciale. Pertanto, la mia logica aziendale non è vincolata dall'ORM o dal livello dati (infatti, l'assembly che contiene ShoppingCart non fa nemmeno riferimento all'EF).

Gli aggiornamenti e le creazioni vengono gestiti tramite i propri modelli di business, ad esempio se è necessario aggiornare un carrello acquisti, ci sarebbe una classe ShoppingCartUpdate che ha regole di convalida e sa solo come mappare le modifiche al modello di database.

Alcuni dati sono memorizzati nella cache in Redis e alcune situazioni richiedono la creazione di oggetti business sia da EF che da Redis. Nell'esempio del carrello acquisti, ad esempio, alcune logiche di business potrebbero dover solo sapere se un utente ha un carrello della spesa o no, ma altre logiche di business che devono riassumere il prezzo si basano su quelle informazioni extra disponibili.

Voglio evitare l'uso di bool come HasDataLoaded , e voglio anche evitare i tipi nullable che inevitabilmente portano a ripetere i nullcheck e le istruzioni in tutto il codebase.

Peggio ancora, se un app dimentica un controllo if / null, potrebbe potenzialmente tentare di spedire un ordine vuoto o qualcosa di stupido in quel modo.

Ecco perché ho pensato che sarebbe stato un approccio migliore per rafforzare la presenza o l'assenza di dati utilizzando classi univoche: un metodo di checkout avrebbe solo un parametro ShoppingCartWithContents , garantendo così che il contenuto sia stato caricato.

Spero che abbia un senso.

    
posta Bio2hazard 30.01.2016 - 06:12
fonte

2 risposte

3

Guardando i tuoi nomi, sembra che l'antica ereditarietà normale faccia il trucco, specialmente se vuoi essere in grado di elaborare un'istanza di ShoppingCartWithContents in un metodo che si interessa solo delle proprietà di ShoppingCart.

    
risposta data 30.01.2016 - 06:20
fonte
1

Please let me know what the best practice in such a situation is.

Non so " la best practice", ma suona sicuramente come lo pattern di progettazione dello stato . Citazione dal link:

Intent

  • Permetti ad un oggetto di alterarne il comportamento quando è interno i cambiamenti. Sembra che l'oggetto cambi la sua classe.

  • Una macchina a stati orientata agli oggetti

  • wrapper + wrappee polimorfico + collaborazione

Osservo che lo schema di stato è un cambiamento di comportamento polimorfico in risposta al cambiamento di stato; non è solo la composizione e la risoluzione del dibattito sull'eredità.

Tuttavia il modello (tutti i modelli) è una guida non gospel quindi non direi che la variazione devia dalla " la best practice".

Basically, I want to make sure on a compiler level that DataFromAssemblyB is present.

La classe base è abstract . I pensa che il pattern suggerisca che ShoppingCart e ShoppingCartWithContent ereditino entrambi da questa base. Tra parametri del costruttore, membri abstract e membri virtual (C # gergo) suona come la tua preoccupazione è coperta.

I want to avoid mapping all properties around by hand, as that could introduce bugs should a developer decide to add a property to ShoppingCart, but forget to map that property in ShoppingCartWithContents.

Forse la mappatura - soluzione AutoMapper di @tmack per esempio - sarebbe nella ShoppingCartWithContents con default codice nella base per attivarlo in un costruttore. Questo tipo suona come il pattern del visitatore , almeno concettualmente (non so come faccia AutoMapper).

    
risposta data 09.02.2016 - 15:02
fonte

Leggi altre domande sui tag