Introduction
The Specification Pattern is a design pattern commonly used in software development to represent and evaluate business rules or conditions in a reusable and composable way.
It provides a formalised approach for defining criteria that objects must meet. This pattern is particularly useful in scenarios where you need to filter or query data based on complex rules or conditions.
Key Concepts
Here are the key components and concepts of the Specification Pattern:
- Specification: The “Specification” interface or class is at the heart of the pattern. This represents a condition or rule that an object needs to satisfy. Typically, it includes a method like
IsSatisfiedBy(T candidate)
that returns a boolean indicating whether the condition is met. - Concrete Specifications: These are implementations of the Specification interface, each representing a specific condition or rule. For example, you might have a
CustomerIsPremiumSpecification
, aProductIsInStockSpecification
, or aOrderIsPendingApprovalSpecification
. Each concrete specification encapsulates a particular business rule. - Logical Combinators: Specifications can be combined using logical operators like AND, OR, and NOT to create more complex conditions. This allows you to build intricate rules by composing simpler specifications. For instance, you can create a
CustomerIsPremiumAndProductIsInStockSpecification
by combining the two individual specifications with an AND operator. - Client Code: The client code, often part of your application or domain logic, uses specifications to evaluate objects. You pass an object (e.g., a customer, product, or order) and a specification to determine if the object meets the specified criteria.
Here’s a simplified example in C#:
public interface ISpecification<T>
{
bool IsSatisfiedBy(T candidate);
}
public class Customer
{
public string Name { get; set; }
public bool IsPremium { get; set; }
}
public class CustomerIsPremiumSpecification : ISpecification<Customer>
{
public bool IsSatisfiedBy(Customer customer)
{
return customer.IsPremium;
}
}
public class Product
{
public string Name { get; set; }
public int StockQuantity { get; set; }
}
public class ProductIsInStockSpecification : ISpecification<Product>
{
public bool IsSatisfiedBy(Product product)
{
return product.StockQuantity > 0;
}
}
// Usage
var premiumCustomerSpec = new CustomerIsPremiumSpecification();
var inStockProductSpec = new ProductIsInStockSpecification();
var customer = new Customer { Name = "John", IsPremium = true };
var product = new Product { Name = "Widget", StockQuantity = 5 };
bool isPremiumCustomer = premiumCustomerSpec.IsSatisfiedBy(customer); // true
bool isProductInStock = inStockProductSpec.IsSatisfiedBy(product); // true
In more complex scenarios, you can combine specifications to create more advanced rules. For instance, you might create a specification to check if a customer is premium and has placed an order within the last 30 days, combining the CustomerIsPremiumSpecification
with a CustomerHasPlacedOrderWithinLast30DaysSpecification
using logical AND.
The Specification Pattern promotes the separation of concerns by isolating business rules and conditions from the rest of the application logic. It encourages reusability and maintainability of these rules and simplifies the process of creating complex queries or filters based on multiple conditions.
I have written other posts about patterns here: – The Factory Method Pattern, The Singleton Pattern, The Facade Pattern.