Reputation: 871
I have the following basic entities:
public class Basket
{
public List<Product> Products {get;set;}
}
public class Product
{
public string Name {get;set;}
public decimal Price {get;set;}
}
And I want to get a list of all products in a basket that are below a fixed price. Should the logic for this go in the Basket
, like so:
public class Basket
{
public List<Product> Products {get;set;}
public List<Product> CheapProducts
{
get { return Products.Where(p => p.Price < 5).ToList(); }
}
}
Or should it go in a service class, ProductFilterer
, which would take the entire list of products as a parameter and would return a filtered list of products. Or maybe it should just go straight into the method of the calling class?
Or something else? What is the best practice for this?
Upvotes: 4
Views: 577
Reputation: 14072
What I would do is see with a domain expert if the notion of "cheap product" is a first class domain concept and has to be introduced in the ubiquitous language.
If this is the case, Steve's Specification solution solves your problem in an elegant way.
If cheapness is unimportant or not as clearly defined as that (for instance if the cheapness threshold varies across the application), I wouldn't bother creating a specific entity for it and just filter Basket.Products with the relevant criteria when needed in calling code.
Upvotes: 2
Reputation: 6228
You might consider looking into the Specification Pattern. The link has a good example implementation, but in short, the pattern allows you to create complex selection criteria based on simple predicates (or specifications).
A quick (and incomplete) implementation of such a pattern using delegates could be done as such:
public class Specification<T>
{
Func<T, bool> _spec;
public Specification(Func<T, bool> spec)
{
_spec = spec;
}
public bool IsSatisifedBy(T item)
{
return _spec(T);
}
}
// ...
_cheapProductsSpecification = new Specification<Product>(p => p.Price < 5);
var cheapProducts = Basket.Products.Where(p => _cheapProductsSpecification.IsSatifisifedBy(p));
This is, of course, a simple and probably redundant example, but if you add in And, Or, and Not (see the link), you can build complex business logic into specification variables.
Upvotes: 2
Reputation: 24336
Your Basket class should not know how to filter directly, it is correct for it to have an exposed function that allows it to return the results from a ProductFilter as you suggested. The way the code should look is something like this:
class ProductFilter
{
filterCheapProducts(Collection<Product> productsToFilter)
{
return Products.Where(p => p.Price < 5).ToList(); //I assume your code is correct
}
}
class Basket
{
Collection<Product> getCheapProducts()
{
return filter.filterCheapProducts(this.products);
}
}
Upvotes: 0
Reputation: 43743
Yes, I would suggest keeping your DTO's separate from the business logic. I like to think of the data objects as being a completely separate layer from the data access, business, and UI layers. If you had a more general ProductBusiness class, I would recommend just putting it in there unless it's really useful to have a separate filterer class.
Upvotes: 0