I am making a shopping cart. A shopping cart will have a total amount of money you've got to pay for the products you've selected.
I'd like to approach the problem using both OOP (encapsulating it) and more anaemically or functionally (without encapsulation).
I'm using the simplest Product model:
public class Product { public string Name { get; set; } public decimal Price { get; set; } } 1. Using encapsulation (aka rich-domain modeling)
TotalPrice can only be calculated inside the shopping cart class.
a) On the fly calculation
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) On the fly calculation with RecalculateTotalPrice method
Seems less performant than approach above or using computed property. We're executing a loop on each Add and Remove.
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) Computed property
Calculated only when accessed TotalPrice. AddProduct and RemoveProduct don't calculate. The disadvantage is we can't easily persist this with some 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. No encapsulation
Our TotalPrice will be calculated outside the class.
public class ShoppingCart { public ICollection<Product> Products { get; set; } = new List<Product>(); public decimal TotalPrice { get; set; } } Calculation of TotalPrice can happen from anywhere. The advantage of this is we can decouple calculation from the shopping cart and even have special calculator class for it that can be extended with custom calculation rules and so on. Some interface like IShoppingCartCalculator.
Currently, I'm using the 1a approach but I was wondering if making it a POCO would result in more extendable and maintainable code managed from the outside.
No encapsulation results in simpler code that is easier to follow. There's 1 less business layer to maintain. We're losing benefits of encapsulation but we're gaining more possibilities and we're more resistant to changes
So our pipeline works like (request → handler → invoking some calculator service method) instead of (request → handler → invoking some ShoppingCart method)
Should I give up on encapsulation to gain more possibilities and more re-usable classes or even functions in case of functional programming?
Is encapsulation a thing of the past that didn't meet expectations of reality?
I'm open to any suggestions.