Jump to content

Specification: Difference between revisions

From Knowledge Base
Created page with "= Specification Pattern = == Overview == The '''Specification Pattern''' is a software design pattern used to encapsulate business rules, logic, or criteria into a reusable, combinable, and testable format. It provides a clear and modular way to evaluate whether objects meet certain conditions. This pattern is particularly useful in domains with complex validation or filtering requirements. == Key Concepts == * '''Encapsulation of Criteria''': Encapsulates rules or con..."
 
No edit summary
Line 14: Line 14:
* Enables dynamic criteria evaluation at runtime.
* Enables dynamic criteria evaluation at runtime.
* Simplifies unit testing for business rules.
* Simplifies unit testing for business rules.
== Key Points to Focus On ==
# '''Expression Trees''': The pattern leverages Expression<Func<T, bool>> to represent conditions as expressions that can be compiled and executed at runtime. This is a powerful feature in C# and .NET, allowing for dynamic filtering and validation logic without hardcoding conditions.
# '''Combining Specifications''': The And, Or, and Not methods allow you to combine different business rules in a flexible way. This modular approach makes it easy to define complex criteria by combining smaller, reusable specifications.
# '''IsSatisfiedBy''': This method evaluates whether an object satisfies the specification's criteria. This is the core of the Specification Pattern, providing a clean and modular way to handle validation logic.


== Example Implementation ==
== Example Implementation ==

Revision as of 12:04, 18 January 2025

Specification Pattern

Overview

The Specification Pattern is a software design pattern used to encapsulate business rules, logic, or criteria into a reusable, combinable, and testable format. It provides a clear and modular way to evaluate whether objects meet certain conditions. This pattern is particularly useful in domains with complex validation or filtering requirements.

Key Concepts

  • Encapsulation of Criteria: Encapsulates rules or conditions into specification objects.
  • Composability: Allows combining multiple specifications using logical operators such as `AND`, `OR`, and `NOT`.
  • Reusability: Specifications can be reused across different parts of an application.

Benefits

  • Improves code readability and maintainability.
  • Promotes single responsibility by decoupling business rules from domain objects.
  • Enables dynamic criteria evaluation at runtime.
  • Simplifies unit testing for business rules.

Key Points to Focus On

  1. Expression Trees: The pattern leverages Expression<Func<T, bool>> to represent conditions as expressions that can be compiled and executed at runtime. This is a powerful feature in C# and .NET, allowing for dynamic filtering and validation logic without hardcoding conditions.
  2. Combining Specifications: The And, Or, and Not methods allow you to combine different business rules in a flexible way. This modular approach makes it easy to define complex criteria by combining smaller, reusable specifications.
  3. IsSatisfiedBy: This method evaluates whether an object satisfies the specification's criteria. This is the core of the Specification Pattern, providing a clean and modular way to handle validation logic.

Example Implementation

Below is an example of the Specification Pattern in C# for a domain involving filtering Products.

using System;
using System.Linq.Expressions;

// Specification Interface
public interface ISpecification<T>
{
    bool IsSatisfiedBy(T entity);
    ISpecification<T> And(ISpecification<T> other);
    ISpecification<T> Or(ISpecification<T> other);
    ISpecification<T> Not();
}

// Base Specification Class
public abstract class Specification<T> : ISpecification<T>
{
    public abstract Expression<Func<T, bool>> ToExpression();

    public bool IsSatisfiedBy(T entity)
    {
        var predicate = ToExpression().Compile();
        return predicate(entity);
    }

    public ISpecification<T> And(ISpecification<T> other) =>
        new AndSpecification<T>(this, other);

    public ISpecification<T> Or(ISpecification<T> other) =>
        new OrSpecification<T>(this, other);

    public ISpecification<T> Not() =>
        new NotSpecification<T>(this);
}

// Concrete Specification
public class ProductHasMinimumPriceSpecification : Specification<Product>
{
    private readonly decimal _minPrice;

    public ProductHasMinimumPriceSpecification(decimal minPrice)
    {
        _minPrice = minPrice;
    }

    public override Expression<Func<Product, bool>> ToExpression()
    {
        return product => product.Price >= _minPrice;
    }
}

// Example of Combining Specifications
public class AndSpecification<T> : Specification<T>
{
    private readonly ISpecification<T> _left;
    private readonly ISpecification<T> _right;

    public AndSpecification(ISpecification<T> left, ISpecification<T> right)
    {
        _left = left;
        _right = right;
    }

    public override Expression<Func<T, bool>> ToExpression()
    {
        var leftExpression = _left.ToExpression();
        var rightExpression = _right.ToExpression();
        var parameter = Expression.Parameter(typeof(T));
        var body = Expression.AndAlso(
            Expression.Invoke(leftExpression, parameter),
            Expression.Invoke(rightExpression, parameter));
        return Expression.Lambda<Func<T, bool>>(body, parameter);
    }
}

// Example Usage
public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class Program
{
    public static void Main()
    {
        var expensiveProductSpec = new ProductHasMinimumPriceSpecification(100);
        var product = new Product { Name = "Laptop", Price = 150 };

        if (expensiveProductSpec.IsSatisfiedBy(product))
        {
            Console.WriteLine($"{product.Name} meets the criteria.");
        }
        else
        {
            Console.WriteLine($"{product.Name} does not meet the criteria.");
        }
    }
}

Advantages

  • Decouples complex rules from business entities.
  • Supports runtime evaluation and dynamic rule composition.
  • Encourages clean architecture and separation of concerns.

Use Cases

  • Validation: Checking whether an object satisfies certain rules.
  • Filtering: Querying data with complex criteria.
  • Authorization: Determining user permissions based on rules.

Related Patterns

  • Strategy Pattern: Both patterns encapsulate behavior but serve different purposes.
  • Interpreter Pattern: Both patterns deal with logic encapsulation, though Specification focuses on evaluation rather than translation.

Further Reading