let's assume having to support some legacy code that has a lot of conditional logic and poor quality.
as well it's ongoing development on the system so It can be quite hard to integrate new rules as the code can be difficult to understand and to digest what is going on.
This sort of code often has comments explaining what the different pieces of conditional logic are doing.
The problems only gets worse as you have to add more conditions over time.
You can get a better understanding of how the Rules Pattern works by checking out the code in my github repo.
the example we have about e-commerce store that has discount criteria feature
public class DiscountCalculator
{
public decimal CalculateDiscountPercentage(Customer customer)
{
decimal discount = 0;
if (customer.DateOfBirth < DateTime.Now.AddYears(-65))
{
// senior discount 5%
discount = .05m;
}
if (customer.DateOfBirth.Day == DateTime.Today.Day &&
customer.DateOfBirth.Month == DateTime.Today.Month)
{
// birthday 10%
discount = Math.Max(discount, .10m);
}
if (customer.DateOfFirstPurchase.HasValue)
{
if (customer.DateOfFirstPurchase.Value < DateTime.Now.AddYears(-1))
{
// after 1 year, loyal customers get 10%
discount = Math.Max(discount, .10m);
if (customer.DateOfFirstPurchase.Value < DateTime.Now.AddYears(-5))
{
// after 5 years, 12%
discount = Math.Max(discount, .12m);
if (customer.DateOfFirstPurchase.Value < DateTime.Now.AddYears(-10))
{
// after 10 years, 20%
discount = Math.Max(discount, .2m);
}
}
if (customer.DateOfBirth.Day == DateTime.Today.Day &&
customer.DateOfBirth.Month == DateTime.Today.Month)
{
// birthday additional 10%
discount += .10m;
}
}
}
else
{
// first time purchase discount of 15%
discount = Math.Max(discount, .15m);
}
return discount;
}
}
The Rules Design Pattern
the fact that this complexity will increase as more rules are added.
I was looking around for a design pattern that might address those things and came across the Rules Pattern
The Rules Pattern works by separating out the rules from the rules processing logic applying the Single Responsibility Principle.
This makes it easy to add new rules without changing the rest of the system applying the Open/Closed Principle.
this image of new design
illustration of the above image:
IRule : contains all methods of rule flow such
ClearConditions()
,Initialize()
,IsValid()
,Apply()
,OnRuleViolated()
as well you can addOnRuleCompleted()
IRule implemented by abstract class
BaseRule<T>
that use dependency ofICondition
that has expression of rule requirementRulesEngine.cs has
ApplyRule()
which the execution path of rulethe rules classes itself inherit base rule and override the method body
BirthdayDiscountRule example:
public class BirthdayDiscountRule : BaseRule<Customer>
{
public override void Initialize(Customer obj)
{
Conditions.Add(new ConditionExtension(CustomerExtensions.IsBirthday(obj)));
}
public override Customer Apply(Customer obj)
{
obj.Discount += .10m;
return obj;
}
public override void OnRuleViolated(Customer obj)
{
Console.WriteLine($"{this.GetType().Name} Violated");
}
}
this code snipped of final result
class Program
{
static void Main(string[] args)
{
var productA = new Product { Price = 100, Name = "Apple Watch" };
var productB = new Product { Price = 50, Name = "Samsung Watch" };
var customer = new Customer
{
Products = new List<Product> { productA, productB, productA, productA },
DateOfFirstPurchase = DateTime.Now.AddYears(-2),
DateOfBirth = DateTime.Now.AddYears(-5),
};
customer.TotalValue = customer.Products.Sum(p => p.Price);
var ruleManager = new CustomerRuleManager(customer);
var result = ruleManager.Run();
}
}
public class CustomerRuleManager
{
private readonly Customer _customer;
public CustomerRuleManager(Customer customer)
{
_customer = customer;
}
public Customer Run()
{
_customer
.ApplyRule(new BirthdayDiscountRule())
.ApplyRule(new BuyThreeGetThirdFree("Apple Watch"))
.ApplyRule(new FreeShipping())
.ApplyRule(new LoyalCustomerRule(5, 0.12m))
.ApplyRule(new NewCustomerRule())
.ApplyRule(new RetiredDiscountRule());
return _customer;
}
}
Top comments (0)