I have following C# code with .Net 4.0
framework. This is created after referring The Specification Pattern - by Jeff Perrin
In the GetProducts()
the conditions to be used are defined (hard coded) inside the method. There is another method named GetProductsBasedOnInputFilters()
. In this method the list of specifications are made as parameter to the method.
QUESTION
What is the best way to apply these filters on the list of products, in this method?
Note: I have tried applying the FindAll
clause inside a foreach
loop and adding the results in a list
. But that logic is incorrect - only those items that satisfy all of the conditions need to be returned.
Note: The number of specifications in the productSpeifications list will vary based on user input
Note: The Approach mentioned in "Dynamically build LINQ filter for the Any() method?" seems useful. However I am not sure how to use this approach here since I am dealing with List of specifications
; not generic delegates
.
Filter Methods
public static class ProductFilterHelper
{
public static List<Product> GetProducts(List<Product> list)
{
double priceLimit = 100;
//FIRST::
//List<Product> selectedList = list.FindAll(new OnSaleSpecification().IsSatisfiedBy);
//SECOND::
//AndSpecification<Product> spec = new AndSpecification<Product>(new OnSaleSpecificationForProduct(), new PriceGreaterThanSpecificationForProduct(priceLimit));
//List<Product> selectedList = list.FindAll(spec.IsSatisfiedBy);
//THIRD:
List<Product> selectedList = list.FindAll(new OnSaleSpecificationForProduct()
.And(new PriceGreaterThanSpecificationForProduct(priceLimit))
.And(new PriceGreaterThan105())
.IsSatisfiedBy
);
return selectedList;
}
public static List<Product> GetProductsBasedOnInputFilters(List<Product> productList, List<Specification<Product>> productSpeifications)
{
List<Product> selectedList = new List<Product>();
foreach (Specification<Product> specification in productSpeifications)
{
List<Product> currentList = productList.FindAll(specification.IsSatisfiedBy);
if (currentList != null && currentList.Count > 0)
{
foreach (Product p in currentList)
{
if (!selectedList.Contains(p))
{
selectedList.Add(p);
}
}
}
}
return selectedList;
}
}
Client
class Program
{
static void Main(string[] args)
{
List<Product> list = new List<Product>();
Product p1 = new Product(false, 99);
Product p2 = new Product(true, 99);
Product p3 = new Product(true, 101);
Product p4 = new Product(true, 110);
Product p5 = new Product(false, 110);
list.Add(p1);
list.Add(p2);
list.Add(p3);
list.Add(p4);
list.Add(p5);
double priceLimit = 100;
List<Specification<Product>> specifications = new List<Specification<Product>>();
specifications.Add(new OnSaleSpecificationForProduct());
specifications.Add(new PriceGreaterThanSpecificationForProduct(priceLimit));
specifications.Add(new PriceGreaterThan105());
List<Product> selectedList = ProductFilterHelper.GetProductsBasedOnInputFilters(list, specifications);
Console.ReadKey();
}
}
Abstract Specifications
public abstract class Specification<T>
{
public abstract bool IsSatisfiedBy(T obj);
public AndSpecification<T> And(Specification<T> specification)
{
return new AndSpecification<T>(this, specification);
}
public OrSpecification<T> Or(Specification<T> specification)
{
return new OrSpecification<T>(this, specification);
}
public NotSpecification<T> Not(Specification<T> specification)
{
return new NotSpecification<T>(this, specification);
}
}
public abstract class CompositeSpecification<T> : Specification<T>
{
protected readonly Specification<T> _leftSide;
protected readonly Specification<T> _rightSide;
public CompositeSpecification(Specification<T> leftSide, Specification<T> rightSide)
{
_leftSide = leftSide;
_rightSide = rightSide;
}
}
Generic Specifications
public class AndSpecification<T> : CompositeSpecification<T>
{
public AndSpecification(Specification<T> leftSide, Specification<T> rightSide)
: base(leftSide, rightSide)
{
}
public override bool IsSatisfiedBy(T obj)
{
return _leftSide.IsSatisfiedBy(obj) && _rightSide.IsSatisfiedBy(obj);
}
}
public class OrSpecification<T> : CompositeSpecification<T>
{
public OrSpecification(Specification<T> leftSide, Specification<T> rightSide)
: base(leftSide, rightSide)
{
}
public override bool IsSatisfiedBy(T obj)
{
return _leftSide.IsSatisfiedBy(obj) || _rightSide.IsSatisfiedBy(obj);
}
}
public class NotSpecification<T> : CompositeSpecification<T>
{
public NotSpecification(Specification<T> leftSide, Specification<T> rightSide)
: base(leftSide, rightSide)
{
}
public override bool IsSatisfiedBy(T obj)
{
return _leftSide.IsSatisfiedBy(obj) && !_rightSide.IsSatisfiedBy(obj);
}
}
Product Specifications
public class OnSaleSpecificationForProduct : Specification<Product>
{
public override bool IsSatisfiedBy(Product product)
{
return product.IsOnSale;
}
}
public class PriceGreaterThanSpecificationForProduct : Specification<Product>
{
private readonly double _price;
public PriceGreaterThanSpecificationForProduct(double price)
{
_price = price;
}
public override bool IsSatisfiedBy(Product product)
{
return product.Price > _price;
}
}
public class PriceGreaterThan105 : Specification<Product>
{
public override bool IsSatisfiedBy(Product product)
{
return product.Price > 105;
}
}
Entity
public class Product
{
private bool _isOnSale;
private double _price = 0.0;
public Product(bool isOnSale)
: this(isOnSale, 0.0)
{
_isOnSale = isOnSale;
}
public Product(double price)
: this(false, price)
{
_price = price;
}
public Product(bool isOnSale, double price)
{
_price = price;
_isOnSale = isOnSale;
}
public bool IsOnSale
{
get { return _isOnSale; }
}
public double Price
{
get { return _price; }
}
}
REFERENCES
You can do one of several things:
Combine the filters by stacking
Where
invocations on top of each other, like in @Lijo's answerCheck all specifications on each item:
return productList .Where(p => specifications.All(ps => ps.IsSatisfiedBy(p)) .ToList()
Create a composite 'And' specification that accepts multiple children instead of just two:
public class AndSpecification<T> : ISpecification<T> { private ISpecification<T>[] _components; public AndSpecification(ISpecification<T>[] components) { _components = components; } public bool IsSatisfiedBy(T item) { return components.All(c => c.IsSatisfiedBy(item)); } }
Then you could do:
var allFiltersSpecification = new AndSpecification(specifications)
return productList.Where(allFiltersSpecification.IsSatisfiedBy);
Following code works... Suggestions are welcome.
public static List<Product> GetProductsBasedOnInputFilters(List<Product> productList, List<Specification<Product>> productSpecifications)
{
IEnumerable<Product> selectedList = productList;
foreach (Specification<Product> specification in productSpecifications)
{
selectedList = selectedList.Where(specification.IsSatisfiedBy);
}
return selectedList.ToList();
}
It is worth to take a look at the following too..
来源:https://stackoverflow.com/questions/21384357/how-to-apply-multiple-filter-conditions-simultaneously-on-a-list