The Specification Pattern

The Specification Pattern

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:

  1. 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.
  2. Concrete Specifications: These are implementations of the Specification interface, each representing a specific condition or rule. For example, you might have a CustomerIsPremiumSpecification, a ProductIsInStockSpecification, or a OrderIsPendingApprovalSpecification. Each concrete specification encapsulates a particular business rule.
  3. 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.
  4. 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.

Stephen

Hi, my name is Stephen Finchett. I have been a software engineer for over 30 years and worked on complex, business critical, multi-user systems for all of my career. For the last 15 years, I have been concentrating on web based solutions using the Microsoft Stack including ASP.Net, C#, TypeScript, SQL Server and running everything at scale within Kubernetes.