Calcolo del prezzo totale degli articoli in un carrello

0

Sto facendo un carrello della spesa. Un carrello della spesa avrà un importo totale che devi pagare per i prodotti che hai selezionato.

Mi piacerebbe affrontare il problema usando entrambi gli OOP (incapsulandolo) e più anemicamente o funzionalmente (senza incapsulamento).

Sto utilizzando il modello di prodotto più semplice:

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

1. Utilizzo dell'incapsulamento (ovvero modellazione di domini avanzati)

TotalPrice può essere calcolato solo all'interno della classe del carrello degli acquisti.

a) Calcolo al volo

public class ShoppingCart
{
    private List<Product> _products = new List<Product>();

    public decimal TotalPrice { get; private set; }
    public IReadOnlyCollection<Product> Products => _products;

    public void AddProduct(Product product)
    {
        _products.Add(product);
        TotalPrice += product.Price;
    }

    public void RemoveProduct(Product product)
    {
        _products.Remove(product);
        TotalPrice -= product.Price;
    }
}

b) Calcolo al volo con il metodo RecalculateTotalPrice

Sembra meno performante dell'approccio sopra o usando la proprietà calcolata. Stiamo eseguendo un ciclo su Aggiungi e Rimuovi.

public class ShoppingCart
{
    private List<Product> _products = new List<Product>();
    public decimal TotalPrice { get; private set; }

    public void AddProduct(Product product)
    {
        _products.Add(product);
        RecalculateTotalPrice();
    }

    public void RemoveProduct(Product product)
    {
        _products.Remove(product);
        RecalculateTotalPrice();
    }

    private void RecalculateTotalPrice()
    {
        var totalPrice = 0m;
        foreach (var product in _products)
        {
            totalPrice += product.Price;
        }
        TotalPrice = totalPrice;
    }
}

c) Proprietà calcolata

Calcolato solo quando si accede a TotalPrice . AddProduct e RemoveProduct non calcolati. Lo svantaggio è che non possiamo facilmente persistere con un ORM.

public class ShoppingCart
{
    private List<Product> _products = new List<Product>();
    public decimal TotalPrice => CalculateTotalPrice();

    public void AddProduct(Product product)
    {
        _products.Add(product);
    }

    public void RemoveProduct(Product product)
    {
        _products.Remove(product);
    }

    private decimal CalculateTotalPrice()
    {
        var totalPrice = 0m;
        foreach (var product in _products)
        {
            totalPrice += product.Price;
        }
        return totalPrice;
    }
}

2. Nessun incapsulamento

Il nostro TotalPrice verrà calcolato al di fuori della classe.

public class ShoppingCart
{
    public ICollection<Product> Products { get; set; } = new List<Product>();
    public decimal TotalPrice { get; set; }
}

Il calcolo di TotalPrice può avvenire da qualsiasi luogo. Il vantaggio di questo è che possiamo disaccoppiare il calcolo dal carrello della spesa e persino avere una speciale classe calcolatrice per esso che può essere estesa con regole di calcolo personalizzate e così via. Alcune interfacce come IShoppingCartCalculator .

Attualmente, sto usando l'approccio 1a, ma mi chiedevo se renderlo un POCO avrebbe comportato un codice più estensibile e gestibile gestito dall'esterno.

Nessun incapsulamento produce un codice più semplice che è più facile da seguire. C'è un livello aziendale in meno per mantenere . Stiamo perdendo i vantaggi dell'incapsulamento, ma stiamo acquisendo più possibilità e siamo più resistenti ai cambiamenti

Quindi la nostra pipeline funziona come (richiesta → gestore → invocazione di un metodo di servizio della calcolatrice) anziché (richiesta → gestore → richiamo di un metodo ShoppingCart)

Dovrei rinunciare all'incapsulamento per ottenere più possibilità e più classi riutilizzabili o persino funzioni in caso di programmazione funzionale?

L'incapsulamento è una cosa del passato che non ha soddisfatto le aspettative della realtà?

Sono aperto a qualsiasi suggerimento.

    
posta Konrad 16.10.2018 - 13:36
fonte

3 risposte

6

A meno che non stiamo parlando di decine di migliaia di prodotti, ricalcolare il prezzo totale ogni volta non vale la pena considerarlo in termini di prestazioni. La mia ipotesi è in una lista della spesa tipica, raramente vedrai più di 10 articoli. Tuttavia, se non è il tuo caso, sentiti libero di scrivermi nei commenti e rivaluterò questo aspetto.

Una volta che le prestazioni sono fuori dall'inquadratura, qualcosa che deve essere preso in considerazione è scrivere codice in modo tale da non lasciare potenzialmente dei buchi. Con questo intendo la tua soluzione 1a. Se si dovesse aggiungere un prodotto, quindi modificare il prezzo in seguito , il prezzo totale sarebbe incoerente. Certamente, potresti non farlo mai nel tuo programma, ma stai chiedendo quale sia il metodo migliore. Il codice di scrittura che consente questo tipo di problemi è non il migliore.

La tua soluzione 1b è almeno coerente, ma ricalcolare ogni volta non è necessario. Idealmente per 100 prodotti aggiunti alla lista, hai solo bisogno di calcolarlo una volta sola. Ancora una volta, le prestazioni non sono un problema, ma non consentono di eseguire calcoli inutili se possiamo evitarlo.

La tua soluzione 2 senza incapsulamento significa che dovresti necessariamente portare questa logica altrove, e sembra una cosa molto naturale che questa logica dovrebbe essere presente nel contenitore stesso, almeno se insisti ad avere una proprietà TotalPrice .

Vorrei inclinarmi alla tua soluzione 1c. Il fatto che possa causare problemi con ORM è un controllo tecnico di una soluzione altrimenti eccellente. La maggior parte degli ORM fornisce un mezzo per ignorare determinate proprietà, quindi sarei sorpreso se non ci fosse qualcosa di simile nel tuo caso. Considerando anche che la necessità di un AddProduct e RemoveProduct non sono necessari, eliminerei questi dalla classe. Se in seguito dovessi sapere se i prodotti sono stati aggiunti o rimossi, puoi utilizzare un elenco osservabile per conservare i prodotti.

Se le prestazioni erano un problema, puoi sempre calcolare pigramente il prezzo totale con un valore booleano interno per indicare se il totale corrente è probabilmente sporco. Quando i prodotti vengono aggiunti o rimossi, viene impostato il flag dirty e si sa che la prossima chiamata a TotalPrice dovrebbe richiedere il ricalcolo.

    
risposta data 16.10.2018 - 14:01
fonte
3

Stai implementando nuovamente ICollection qui.

d) Proprietà Get-only

public class ShoppingCart
{
    public ICollection<Product> Products { get; } = new List<Product>();
    public decimal TotalPrice => Products.Sum(p => p.Price);
}

Il tuo ORM dovrebbe essere in grado di popolare la multa di raccolta Products . Non è necessario essere in grado di riassegnare .

    
risposta data 16.10.2018 - 14:01
fonte
-2

Il tuo esempio con incapsulamento / oop richiede più incapsulamento / oop.

Ovviamente non conosco il tuo caso d'uso in dettaglio, ma un approccio OOP raramente restituirebbe un TotalPrice da ShoppingCart . Il ritorno dei dati non è in realtà simile a quello di Oop.

Inoltre, dominio ricco non significa che tu abbia alcuni metodi aggiuntivi sugli oggetti, significa che hai metodi commerciali sugli oggetti. Metodi aziendali significa azioni che derivano direttamente dai requisiti, dal "linguaggio Ubiquitous".

Quindi anziché TotalPrice , ShoppingCart dovrebbe avere Checkout() . Questo è il motivo per cui abbiamo bisogno del prezzo, e non dovremmo chiedere all'oggetto i dati, dovremmo dirgli cosa fare.

A proposito di se l'incapsulamento è una cosa del passato (supponente): penso che non sia stato ancora dominante. Disponendo solo di "oggetti" di dati (cioè record o strutture), solo sembra più flessibile. Sembra flessibile ora , perché non devi ancora pensare a come verrà usato. La conseguenza di ciò è che sarà usato in qualche modo dappertutto. La conseguenza di ciò a sua volta è che sarà estremamente difficile cambiarlo più tardi, poiché tutto sarà strongmente accoppiato ad esso.

    
risposta data 17.10.2018 - 17:21
fonte

Leggi altre domande sui tag