Demystifying Design Patterns — Rule-based pattern / Rule-engine pattern

CodeChuckle
4 min readJan 2, 2024

--

Photo by Headway on Unsplash

A rule-based software design pattern is an architectural approach that relies on a set of predefined rules or conditions to dictate the behavior of the software. These rules govern decision-making processes, guide system interactions, and influence the flow of the application based on specific criteria. Rule-based systems are prevalent in various domains, from expert systems in artificial intelligence to business rule engines in enterprise applications.

In this exploration, we delve into the essence of the rule-based pattern, uncovering its applications, strengths, and considerations. From dynamic decision-making scenarios to complex business logic and adaptable workflows, we’ll unravel the layers of this design pattern. To illustrate its real-world application, we’ll embark on a journey to create a discount system, showcasing the rule-based pattern in action.

When to Apply Rule-Based Pattern:

Dynamic Decision-Making:

Rule-based patterns shine in scenarios where decision-making is dynamic and depends on changing conditions. Systems that require flexibility in adapting to evolving business rules or user preferences benefit from this approach.

Complex Business Logic:

Applications with intricate business logic, where decisions are influenced by numerous factors, find rule-based patterns advantageous. These patterns help maintain clarity and ease of modification in the face of complex rule sets.

Adaptable Workflow:

Rule-based systems are well-suited for applications that need to accommodate diverse workflows. Whether it’s routing processes, validation checks, or conditional branching, rule-based patterns provide a structured way to manage such complexity.

Why Rule-Based Software Design Patterns are Valuable:

Flexibility and Adaptability:

Rule-based systems excel in environments where requirements evolve. By encapsulating business logic in rules, developers can modify or extend the system’s behavior without extensive code changes, fostering adaptability.

Maintainability:

Rule-based patterns contribute to software maintainability by providing a clear separation of concerns. Rules are typically externalized, making it easier to update, review, and manage them without delving into the core codebase.

Scalability:

In systems with large and intricate rule sets, rule-based patterns offer scalability. The modular nature of rules allows for the addition of new rules or modifications to existing ones without disrupting the entire system.

Why Not Use Rule-Based Software Design Patterns:

Performance Concerns:

In situations where ultra-fast processing is crucial, rule-based systems might introduce a level of indirection that could impact performance. Careful consideration of the application’s performance requirements is essential.

Overcomplexity:

While rule-based systems excel in managing complexity, there’s a risk of over-engineering. In simpler applications or scenarios with static rule sets, a more straightforward design pattern may be more appropriate.

Example:

Problem:

Create a discount system with the following rules:

  1. 10% off for purchases between $25,000 and $50,000.
  2. 15% off for purchases between $50,000 and $75,000.
  3. 20% off for purchases exceeding $75,000.
  4. Flat 5% Christmas discount.

Design a system that automatically applies the most beneficial discount to optimize customer savings.

Solution:

1. Item Class:

This class represents an item in our retail system, holding essential details like ID, name, amount, and item type.

public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Amount { get; set; }
public string ItemType { get; set; }
}

2. IDiscountRule Interface:

The IDiscountRule interface outlines the structure for any discount rule class. Each rule must implement the CalculateDiscount method.

public interface IDiscountRule
{
decimal CalculateDiscount(Item item);
}

3. Discount Rules: Christmas and Amount Discounts:

We have two initial discount rules — one for Christmas and another for amounts based on specific brackets.

public class ChristmasDiscountRule : IDiscountRule
{
public decimal CalculateDiscount(Item item)
{
// Christmas discount is a flat 5%
return 0.05m;
}
}

public class AmountDiscountRule : IDiscountRule
{
public decimal CalculateDiscount(Item item)
{
// Switch statement for different amount brackets
decimal discountRate;

switch (item.Amount)
{
case var amount when amount > 25000 && amount <= 50000:
discountRate = 0.10m;
break;
case var amount when amount > 50000 && amount <= 75000:
discountRate = 0.15m;
break;
case var amount when amount > 75000:
discountRate = 0.20m;
break;
default:
discountRate = 0; // No discount for other amounts
break;
}

return discountRate;
}
}

4. DiscountRuleEngine class

The DiscountRuleEngine maintains a list of discount rules and calculates the maximum discount applicable to an item.

public class DiscountRuleEngine
{
private readonly List<IDiscountRule> discountRules;

public DiscountRuleEngine()
{
discountRules = new List<IDiscountRule>
{
new AmountDiscountRule(),
new ChristmasDiscountRule()
// Add more rules as needed
};
}

public decimal CalculateDiscount(Item item)
{
decimal maxDiscount = 0;

// Iterate through rules to find the maximum discount
foreach (var rule in discountRules)
{
var discount = rule.CalculateDiscount(item);
maxDiscount = Math.Max(maxDiscount, discount);
}

return item.Amount * maxDiscount;
}
}

Now, let’s test our system with two items — a smartphone and a laptop — to see how the discounts are applied.

var item1 = new Item { Id = 1, Name = "Smartphone", Amount = 30000, ItemType = "Electronics" };
var item2 = new Item { Id = 2, Name = "Laptop", Amount = 60000, ItemType = "Clothing" };

decimal discount1 = discountRuleEngine.CalculateDiscount(item1);
decimal discount2 = discountRuleEngine.CalculateDiscount(item2);

Console.WriteLine($"Discount for {item1.Name}: {discount1:C}");
Console.WriteLine($"Discount for {item2.Name}: {discount2:C}");

As we witness our retail discount system seamlessly applying discounts based on rules, the efficacy of the rule-based design pattern becomes evident. Now, let’s wrap up our exploration and draw key insights into the significance of this approach in software design.

Conclusion:

Rule-based software design patterns provide a robust foundation for creating adaptive, maintainable, and scalable applications. By understanding their strengths, knowing when to apply them, and being aware of potential drawbacks, developers can harness the power of rule-based patterns to build software systems that efficiently meet the demands of a dynamic and ever-changing digital landscape.

This content was crafted with the invaluable assistance of ChatGPT.

Please ensure to express your support by giving my blog post a 👏 and following my account.

--

--