Specification Pattern in C#: Mastering Behavioral Design Patterns

Explore the Specification Pattern in C# to create reusable and combinable business rules. Learn how to implement, combine, and apply this pattern for validation and filtering with LINQ.

6.13 Specification Pattern

The Specification Pattern is a powerful tool in the arsenal of software engineers and architects, particularly when dealing with complex business rules and logic. It allows for the encapsulation of business rules into reusable and combinable components, making your codebase more maintainable and flexible. In this section, we will delve into the Specification Pattern, exploring its implementation in C#, how to combine business rules, and practical use cases such as validation and filtering with LINQ.

Specification Pattern Description

The Specification Pattern is a behavioral design pattern that defines a business rule in a separate class, allowing it to be reused and combined with other rules. This pattern is particularly useful when you have complex business logic that needs to be applied to various objects or entities within your application.

Intent

The primary intent of the Specification Pattern is to encapsulate business rules and logic in a way that they can be easily reused, combined, and modified without affecting the core business logic. This separation of concerns enhances the maintainability and scalability of the application.

Key Participants

  • Specification: An interface or abstract class that defines the method(s) for evaluating the business rule.
  • ConcreteSpecification: A class that implements the Specification interface and contains the actual business logic.
  • CompositeSpecification: A class that allows for combining multiple specifications using logical operations such as AND, OR, and NOT.

Implementing Specification in C#

To implement the Specification Pattern in C#, we start by defining a base ISpecification<T> interface that declares a method for evaluating the specification. This is followed by creating concrete specifications that implement this interface.

ISpecification Interface

1public interface ISpecification<T>
2{
3    bool IsSatisfiedBy(T candidate);
4}

The IsSatisfiedBy method takes a candidate object and returns a boolean indicating whether the candidate satisfies the specification.

Concrete Specification

Let’s create a concrete specification for a simple business rule: checking if a product is in stock.

1public class InStockSpecification : ISpecification<Product>
2{
3    public bool IsSatisfiedBy(Product product)
4    {
5        return product.Stock > 0;
6    }
7}

Here, the InStockSpecification checks if the Stock property of a Product object is greater than zero.

Composite Specification

To combine multiple specifications, we can create a CompositeSpecification class that allows logical operations.

 1public abstract class CompositeSpecification<T> : ISpecification<T>
 2{
 3    public abstract bool IsSatisfiedBy(T candidate);
 4
 5    public ISpecification<T> And(ISpecification<T> other)
 6    {
 7        return new AndSpecification<T>(this, other);
 8    }
 9
10    public ISpecification<T> Or(ISpecification<T> other)
11    {
12        return new OrSpecification<T>(this, other);
13    }
14
15    public ISpecification<T> Not()
16    {
17        return new NotSpecification<T>(this);
18    }
19}

Logical Operations

Implement the logical operations as separate classes.

 1public class AndSpecification<T> : CompositeSpecification<T>
 2{
 3    private readonly ISpecification<T> _left;
 4    private readonly ISpecification<T> _right;
 5
 6    public AndSpecification(ISpecification<T> left, ISpecification<T> right)
 7    {
 8        _left = left;
 9        _right = right;
10    }
11
12    public override bool IsSatisfiedBy(T candidate)
13    {
14        return _left.IsSatisfiedBy(candidate) && _right.IsSatisfiedBy(candidate);
15    }
16}
17
18public class OrSpecification<T> : CompositeSpecification<T>
19{
20    private readonly ISpecification<T> _left;
21    private readonly ISpecification<T> _right;
22
23    public OrSpecification(ISpecification<T> left, ISpecification<T> right)
24    {
25        _left = left;
26        _right = right;
27    }
28
29    public override bool IsSatisfiedBy(T candidate)
30    {
31        return _left.IsSatisfiedBy(candidate) || _right.IsSatisfiedBy(candidate);
32    }
33}
34
35public class NotSpecification<T> : CompositeSpecification<T>
36{
37    private readonly ISpecification<T> _specification;
38
39    public NotSpecification(ISpecification<T> specification)
40    {
41        _specification = specification;
42    }
43
44    public override bool IsSatisfiedBy(T candidate)
45    {
46        return !_specification.IsSatisfiedBy(candidate);
47    }
48}

Combining Business Rules

The true power of the Specification Pattern lies in its ability to combine multiple specifications to form complex business rules. Let’s see how we can achieve this.

Building Complex Rules

Suppose we have another specification to check if a product is expensive.

 1public class ExpensiveSpecification : ISpecification<Product>
 2{
 3    private readonly decimal _priceThreshold;
 4
 5    public ExpensiveSpecification(decimal priceThreshold)
 6    {
 7        _priceThreshold = priceThreshold;
 8    }
 9
10    public bool IsSatisfiedBy(Product product)
11    {
12        return product.Price > _priceThreshold;
13    }
14}

Now, we can combine the InStockSpecification and ExpensiveSpecification to create a new rule that checks if a product is both in stock and expensive.

1var inStockSpec = new InStockSpecification();
2var expensiveSpec = new ExpensiveSpecification(1000);
3
4var inStockAndExpensiveSpec = inStockSpec.And(expensiveSpec);
5
6bool isSatisfied = inStockAndExpensiveSpec.IsSatisfiedBy(product);

Use Cases and Examples

The Specification Pattern is versatile and can be applied in various scenarios, such as validation, filtering, and more.

Validation

In scenarios where multiple validation rules need to be applied to an object, the Specification Pattern provides a clean and maintainable approach.

 1public class ProductValidator
 2{
 3    private readonly ISpecification<Product> _specification;
 4
 5    public ProductValidator(ISpecification<Product> specification)
 6    {
 7        _specification = specification;
 8    }
 9
10    public bool Validate(Product product)
11    {
12        return _specification.IsSatisfiedBy(product);
13    }
14}

Filtering with LINQ

The Specification Pattern can be seamlessly integrated with LINQ to filter collections based on complex criteria.

1var products = GetProducts(); // Assume this returns a list of products
2
3var inStockSpec = new InStockSpecification();
4var expensiveSpec = new ExpensiveSpecification(1000);
5
6var filteredProducts = products.Where(p => inStockSpec.And(expensiveSpec).IsSatisfiedBy(p)).ToList();

Visualizing the Specification Pattern

To better understand the Specification Pattern, let’s visualize the relationships between the different components using a class diagram.

    classDiagram
	    class ISpecification<T> {
	        +bool IsSatisfiedBy(T candidate)
	    }
	    class InStockSpecification {
	        +bool IsSatisfiedBy(Product product)
	    }
	    class ExpensiveSpecification {
	        +bool IsSatisfiedBy(Product product)
	    }
	    class CompositeSpecification<T> {
	        +ISpecification<T> And(ISpecification<T> other)
	        +ISpecification<T> Or(ISpecification<T> other)
	        +ISpecification<T> Not()
	    }
	    class AndSpecification<T> {
	        +bool IsSatisfiedBy(T candidate)
	    }
	    class OrSpecification<T> {
	        +bool IsSatisfiedBy(T candidate)
	    }
	    class NotSpecification<T> {
	        +bool IsSatisfiedBy(T candidate)
	    }
	
	    ISpecification<T> <|-- InStockSpecification
	    ISpecification<T> <|-- ExpensiveSpecification
	    ISpecification<T> <|-- CompositeSpecification<T>
	    CompositeSpecification<T> <|-- AndSpecification<T>
	    CompositeSpecification<T> <|-- OrSpecification<T>
	    CompositeSpecification<T> <|-- NotSpecification<T>

Design Considerations

When implementing the Specification Pattern, consider the following:

  • Reusability: Ensure that specifications are designed to be reusable across different parts of the application.
  • Composability: Leverage the pattern’s ability to combine specifications to create complex business rules.
  • Performance: Be mindful of performance implications when combining multiple specifications, especially in large datasets.
  • C# Specific Features: Utilize C# features such as generics and LINQ to enhance the flexibility and expressiveness of your specifications.

Differences and Similarities

The Specification Pattern is often compared to other patterns like the Strategy Pattern and the Composite Pattern. While all these patterns deal with encapsulating behavior, the Specification Pattern is unique in its focus on business rules and logic evaluation.

  • Specification vs. Strategy: The Strategy Pattern is used to encapsulate algorithms, while the Specification Pattern is used to encapsulate business rules.
  • Specification vs. Composite: The Composite Pattern is used to treat individual objects and compositions uniformly, whereas the Specification Pattern is used to combine business rules.

Try It Yourself

To solidify your understanding of the Specification Pattern, try modifying the code examples provided. For instance, create a new specification that checks if a product is on sale and combine it with existing specifications. Experiment with different combinations and see how they affect the outcome.

Knowledge Check

Before we conclude, let’s summarize the key takeaways:

  • The Specification Pattern encapsulates business rules into reusable components.
  • It allows for the combination of multiple specifications to form complex rules.
  • The pattern is particularly useful in scenarios involving validation and filtering.
  • CSharp features such as generics and LINQ enhance the implementation of the Specification Pattern.

Remember, mastering design patterns is a journey. As you continue to explore and apply these patterns, you’ll gain deeper insights into building robust and maintainable software systems. Keep experimenting, stay curious, and enjoy the journey!

Quiz Time!

Loading quiz…
Revised on Thursday, April 23, 2026