0

I am implementing a discount calculation model. One item PER order. I do have a Product class:

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

I do have an Order class as well:

public class Order
{
    public DateTime Date { get; set; }
    public string ProductName { get; set; }
    public Size Size { get; set; }
}

I have a few discounts:

  1. Competitive discount - chooses lowest price amongst all products by size. For example, if Latte S size is 2$ and Expresso S size is 3$ then Expresso S size will cost You 2$, 1$ discount will be applied.
  2. Quantity discount - for x amount one is free in the time period. For example, I buy 2x Latte and 3rd is free and this discount can only be applied daily.

During my implementation, I wanted to use Strategy pattern but I have few nuances:

  1. A Competitive discount is not dependant on the time period, but Quantity discount is.
  2. Quantity discount is dependent on the quantity selected, competitive is not (however I could provide value as 1 in implementation, so it would be applied for every item).

Solution:

IDiscount.cs

public interface IDiscount
{
    public bool IsApplicable(Order order, int itemsCountForDiscount);
    public decimal GetDiscountAmount(Order order);
}

CompetitiveDiscount.cs

public class CompetiveDiscount : IDiscount
{
    Size Size { get; set; }
    decimal LowestProductPrice { get; set; }
    public CompetiveDiscount(Size size)
    {
        Size = size;
        LowestProductPrice = GetLowestProductPriceBySize(size);
    }

    public decimal GetDiscountAmount(Product product)
    {
        return product.Price - LowestPriceProvider;
    }

    public bool IsApplicable(Product product, int itemsCountForDiscount)
    {
        //returns if applicable
    }

    private decimal GetLowestProductPriceBySize(Size size)
    {
        //returns lowest price of products
    }
}

QuantityDiscount.cs

public class QuantityDiscount: IDiscount
{
    string ProductName { get; set; }
    int ItemsCountForDiscount { get; set; }
    Period Period { get; set; } //enum for period

    public QuantityDiscount(string productName, int itemsCountForDiscount, Period period)
    {
        ProductName = productName;
        ItemsCountForDiscount = itemsCountForDiscount;
        Period = period;
    }

    public decimal GetDiscountAmount(Product product)
    {
        return product.Price;
    }

    public bool IsApplicable(Product product, int itemsCountForDiscount)
    {
        //returns if applicable
    }
}

My question would be how could I make this model work if I do need Date in one of the discount, but in another I don't. Is it better to use a Chain of Responsibility design pattern?

2
  • What about discounts that apply to product combinations or orders?
    – svidgen
    Aug 18, 2020 at 16:10
  • Now and forever? :D
    – svidgen
    Aug 18, 2020 at 18:03

1 Answer 1

4

The intent of the Strategy pattern is to encapsulate a family of algorithms and use them interchangeably. But your current need seems to be very different:

  • You want to apply different algorithms that cannot be used interchangeably;
  • You want the right algorithm to be used;
  • Perhaps do you even want to compose algorithms and apply competitive and quantity discount.

If you want nevertheless go for Strategy, you will have to provide a uniform access to the calculation data:

  • You could add for this purpose all possible parameters. But this wouldn't really be a good idea in view of ISP and future evolutions.
  • So, a better alternative is to inject into the parameters a query object that gives access to the current order and the other orders. Up to your strategy to use the same day.

Another approach would be to use a similar approach but using an chain of responsibility instead. Every discount calculator in the chain would find out if it is applicable, and if needed, call the next calculators.

But if I where you, I would not focus at design patterns for this problem. I would go for an ad-hoc princing engine to which you add rules, and let the engine go and find which rules lay apply, how to solve conflicts between competing rules, calculate the applicable discounts and combine them. Once you have designed this engine, you can start to think to refactor it based on REAL DESIGN problems instead of premature choices.

Not related: Having worked a lot with ERP's, I can tell you that discount calculations is terribly complex, seldomly fits in a simple pattern, and your two cases only scratch the surface of what salespeople can invent.

2
  • Thank You for your response, very appreciated! Just was wondering that might there exist a design pattern that could fit better than just going for an ad-hoc option.
    – Marius
    Aug 17, 2020 at 18:29
  • @Marius If you go for a classical rule engine, you'd go for a list of rules, each rule having a verification method to check if it applies and a calculation method if yes. More sofisticated combinations can be used by making the rules according to the interpreter pattern. A very different approach is based on some sequence of condition tables based, and some of those are even patented (because they have a design allowing for very high volume, when the rule based are a little slower). I hope this helps to start.
    – Christophe
    Aug 17, 2020 at 19:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.