Search code examples
c#.netopen-closed-principle

Open closed principle for different filter conditions


The class ProductService, below, gets products based on different filters like by date, country, etc., from a database. ProductsService doesn't follow OCP, as adding new filter like getting products by Price requires changing the ProductsService code. How it can be fixed? Any suggestions/comments would be really helpful.

public class ProductsService : IProductsService
{
    public FilteredProducts GetProductsByDate(DateTime startDate, DateTime EndDate) 
    {   
        //.....

    }
    public FilteredProducts GetProductsByCountry(string country)
    {
        //.....

    }

    public FilteredProducts GetProductsByCity(string city) 
    {
        //.....

    }

}

public class FilteredProducts
{
    public IEnumerable<Product> Products{set;get;}
    public int uniqueProducts { set; get; }
}

public class Product
{
    public int ID{set;get;}
    public string Name{set;get;}
    public decimal Cost{set;get;}
}

Solution

  • Best way is to represent each operation as separate class.

        public interface IProductFilter
        {
            FilteredProducts GetProducts(); 
        }
    
        public class GetProductsByDate : IProductFilter
        {
            private DateTime _startDate;
            private DateTime _endDate;
    
            public GetProductsByDate(DateTime startDate, DateTime EndDate)
            {
                _startDate = startDate;
                _endDate = EndDate;
            }
    
            public FilteredProducts GetProducts()
            {
                // filter
            }
        }
    

    You can then pass this implementation into your service, where it gets executed.

        public class ProductsService : IProductsService
        {
            public FilteredProducts FilterProducts(IProductFilter filter)
            {
                // execute the filter
                // return the products
            }
        }
    

    You could even go as far as turning it into generic Command (for example here) and executing all your database logic through it, ditching the "service" anti-pattern.