Firme di proprietà con carico pigro negli oggetti business

3

Diciamo che sto progettando oggetti business attorno a un database poco ottimizzato che non ha alcuna possibilità di cambiare.

Ho un oggetto Person con una proprietà ShippingAddress . Il ShippingAddress è molto costoso da caricare e si trova in una tabella separata, e una minoranza di pagine lo usa. Ci sono momenti in cui migliaia di oggetti Person devono essere caricati su una singola pagina, quindi mangiare la performance non è un'opzione. Questo rende pigro il caricamento ideale di ShippingAddress per questa situazione.

Tuttavia, dal punto di vista della logica aziendale / consumatore / firma non vi è alcun motivo per cui ShippingAddress debba essere diverso dalla proprietà Nome caricata con entusiasmo. Per questo motivo, ho deciso di rendere la firma di proprietà pubblica di ShippingAddress come una stringa regolare mentre il suo campo di backing privato è Lazy di C #. La domanda è, come devo impostarla? Ho creato due opzioni che mantengono la separazione della logica di accesso ai dati:

L'opzione 1 , utilizzando il metodo di set di stile java sovraccarico, mi sembra migliore OO e più coerente da una prospettiva interna poiché sfrutto l'overloading dei metodi:

    Lazy<string> _address;
    public string Address { get { return _address == null ? null : _address.Value; } }
    public void SetAddress(Func<string> address)
    {
        _address = new Lazy<string>(address);
    }
    public void SetAddress(string address)
    {
        SetAddress(() => address);
    }

L'opzione 2 sembra molto migliore / coerente dal punto di vista del consumatore, ma i due metodi di impostazione sembrano molto disgiunti in un modo che mi dà fastidio:

    Lazy<string> _address;
    public string Address 
    { 
        get { return _address == null ? null : _address.Value; }
        set { SetAddress(() => value); }
    }
    public void SetAddress(Func<string> address)
    {
        _address = new Lazy<string>(address);
    }

Qualcuno con più esperienza di me (la maggior parte di voi presumo) che usa la classe pigra di C # ha qualche idea su quale sia il migliore e perché?

    
posta TheCatWhisperer 05.06.2015 - 21:42
fonte

2 risposte

3

Quando si tratta di una BO, il consumatore non dovrebbe vedere il campo è pigro caricato o meno ed è meglio presentare come qualsiasi altra proprietà (senza esporre i metodi accettando i parametri Func). Preferirei andare con il secondo approccio senza avere il metodo pubblico SetAddress

Lazy<string> _address;

public string Address 
{ 
    get { return _address == null ? null : _address.Value; }
    set { _address = new Lazy<string>(() => value); }
}
    
risposta data 06.06.2015 - 14:24
fonte
4

In primo luogo, un'alternativa

The ShippingAddress is very expensive to load and located in a separate table, and a minority of pages use it.

In questo caso, forse ShippingAddress non appartiene a Person in termini di modello di Business Object? Per i casi in cui è necessario coinvolgere ShippingAddress come un concetto di prima classe, è possibile invece definire un oggetto business ShippingAddress , che può naturalmente avere una Person come dipendenza se necessario, o meglio ancora rappresentare solo Spedisci gli indirizzi come servizio, caricali quando necessario (in base a PersonID o qualsiasi altra cosa) e li allega direttamente all'istanza Person .

Secondo, un caso eccezionale

Se ciò non è / non può far galleggiare la tua capra, allora continuiamo con Caricamento pigro. Non sono d'accordo con la tua affermazione che "da una prospettiva di business logic / consumer / signature non ci sono motivi per cui ShippingAddress dovrebbe essere diversa dalla proprietà Name caricata avidamente".

Vedo totalmente la prospettiva; e in generale sarei d'accordo - tranne che in questo caso hai una situazione specifica, nel mondo reale (accesso orribilmente lento all'archivio dati di indirizzi di spedizione, non può essere cambiato, queste cose accadono) che sfortunatamente sfida la normale aspettativa che se tu abbia visto un getter , li hai visti tutti:)

Quindi direi che un client che accede alla proprietà Address ** sarebbe sorpreso di scoprire che in realtà comporta un hit di database separato, specialmente considerando

  • Le altre proprietà non
  • Questa è una proprietà su un oggetto business, non un'entità ovviamente "connessa".

TL; DR - non violare POLA - considera cosa potrebbe accadere se qualcuno lancia un gruppo di Person oggetti in XmlSerialiser ....

Terzo, un approccio suggerito

Se utilizzi il caricamento lazy dal database per l'indirizzo, ti consigliamo uno dei due approcci, entrambi i quali implicano che il caricatore vero e proprio è un'astrazione esplicitamente modellata.

1. Rendi la funzione di caricamento una dipendenza esplicita di Person

public Person(int name, etc, IShippingAddressLoader addressLoader)

2. Rendi la funzione di caricamento una dipendenza esplicita di un metodo LoadAddress ()

public void LoadAddress(IShippingAddressLoader addressLoader) { _address=addressLoader.Load(_personID); //or whatever; }

Il mio ragionamento è

  • In entrambi i modi, penso che sia più ovvio al codice client che usa la classe Person che ci sia un codice "extra" coinvolto nell'ottenere l'indirizzo di spedizione; e come bonus rende la classe Person molto più unità testabile perché puoi prendere in giro o bloccare il loader.

  • Con entrambi gli approcci non è necessario dichiarare _address se stesso come Lazy perché hai spostato la "pigrizia" su un concetto esplicito (il loader).

  • Quando trovi un buon modo per mettere in cache tutti gli indirizzi (forse il tuo server ha più RAM, così ora è possibile caricarli tutti in memoria all'avvio dell'app, dare uno SqlDipendenza su di essi e fare attenzione alle modifiche ), è sufficiente modificare l'implementazione IShippingAddressLoader, non la classe Person .

Finalmente un tentativo subdolo

per venderti la prima idea suggerendo di accettare che l'indirizzo di spedizione è un dato difficile nel tuo caso e che qualsiasi codice che ha a che fare con esso (controller o whatnot) ottiene solo il proprio IShippingAddressService servizio, ad es.

public ShippingConfirmationModel GenerateConfirmationModel(Person person)
{
    var shippingAddress = _shippingAddressService.GetByPersonID(person.ID);
    person.SetShippingAddress(shippingAddress)
    return new ShippingConfirmationModel(person);
}

Questo ti libera da tutte le imitazioni legate al caricamento lazy nella classe Person, ti consente di implementare la memorizzazione nella cache in modo semplice e ti permette anche di passare oggetti Person in sicurezza sapendo che non accederai accidentalmente al database quando qualcuno decide di serializzare tutte le persone:)

** Nota secondaria - se si tratta di "Indirizzo di spedizione" della persona, farei in modo di riferirlo sempre come "Indirizzo di spedizione" nel codice, anziché solo "Indirizzo":)

    
risposta data 07.06.2015 - 02:32
fonte

Leggi altre domande sui tag