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