Implementing Specification Pattern in .NET Core
Introduction
The Specification Pattern is a powerful design pattern that helps improve the flexibility and maintainability of code by encapsulating complex business rules and conditions. In this blog post, we will explore the concept of the Specification Pattern and demonstrate its implementation in .NET Core using practical examples.
What is Specification Pattern?
The Specification Pattern is a behavioural design pattern that allows developers to define business rules as separate entities and combine them to form complex conditions. By doing so, it promotes the separation of concerns and enhances code reusability and readability.
Key Concepts of DDD
The Specification Pattern typically involves the following components:
- Specification:
An interface or abstract class representing a business rule or condition. - Concrete Specifications:
Classes that implement the Specification interface and encapsulate specific business rules. - Composite Specifications:
Classes that combine multiple specifications using logical operators (AND, OR, NOT) to form complex conditions.
- Specification:
Implementing the Specification Pattern in .NET Core
Creating the Specification Interface
First, let’s define the Specification interface:
public interface ISpecification<T>
{
bool IsSatisfiedBy(T item);
}
Implementing Concrete Specifications
Next, let’s create some concrete specifications that implement the ISpecification interface:
public class PriceSpecification : ISpecification<Product>
{
private readonly decimal _minPrice;
private readonly decimal _maxPrice;
public PriceSpecification(decimal minPrice, decimal maxPrice)
{
_minPrice = minPrice;
_maxPrice = maxPrice;
}
public bool IsSatisfiedBy(Product item)
{
return item.Price >= _minPrice && item.Price <= _maxPrice;
}
}
public class CategorySpecification : ISpecification<Product>
{
private readonly string _category;
public CategorySpecification(string category)
{
_category = category;
}
public bool IsSatisfiedBy(Product item)
{
return item.Category == _category;
}
}
Creating Composite Specifications
Now, let’s create some composite specifications to combine multiple specifications:
public class AndSpecification<T> : ISpecification<T>
{
private readonly ISpecification<T> _specification1;
private readonly ISpecification<T> _specification2;
public AndSpecification(ISpecification<T> specification1, ISpecification<T> specification2)
{
_specification1 = specification1;
_specification2 = specification2;
}
public bool IsSatisfiedBy(T item)
{
return _specification1.IsSatisfiedBy(item) && _specification2.IsSatisfiedBy(item);
}
}
public class OrSpecification<T> : ISpecification<T>
{
private readonly ISpecification<T> _specification1;
private readonly ISpecification<T> _specification2;
public OrSpecification(ISpecification<T> specification1, ISpecification<T> specification2)
{
_specification1 = specification1;
_specification2 = specification2;
}
public bool IsSatisfiedBy(T item)
{
return _specification1.IsSatisfiedBy(item) || _specification2.IsSatisfiedBy(item);
}
}
public class NotSpecification<T> : ISpecification<T>
{
private readonly ISpecification<T> _specification;
public NotSpecification(ISpecification<T> specification)
{
_specification = specification;
}
public bool IsSatisfiedBy(T item)
{
return !_specification.IsSatisfiedBy(item);
}
}
Applying Specification Pattern in .NET Core
Example-1: Filtering Products
Let’s use the Specification Pattern to filter a list of products based on specific criteria:
// Assume we have a Product class as follows:
public class Product
{
public string Name { get; set; }
public string Category { get; set; }
public decimal Price { get; set; }
}
// Sample products list
var products = new List<Product>
{
new Product { Name = “Laptop”, Category = “Electronics”, Price = 1000 },
new Product { Name = “Smartphone”, Category = “Electronics”, Price = 700 },
new Product { Name = “Book”, Category = “Books”, Price = 25 }
};
// Creating specifications
var electronicsSpec = new CategorySpecification(“Electronics”);
var affordableSpec = new PriceSpecification(0, 800);
// Combining specifications
var electronicsAndAffordableSpec = new AndSpecification<Product>(electronicsSpec, affordableSpec);
// Filtering products using the composite specification
var filteredProducts = products.Where(p => electronicsAndAffordableSpec.IsSatisfiedBy(p)).ToList();
Advantages of Using Specification Pattern
- Enhanced Readability:
The Specification Pattern improves code readability by encapsulating complex conditions into separate classes, making business rules easier to understand. - Re-usability:
With individual specifications defined as separate classes, they can be reused in various parts of the application, reducing code duplication. - Maintainability:
Separating business rules from the rest of the code base simplifies maintenance and updates, as changes to specifications won’t affect other components.
- Enhanced Readability:
Conclusion
Specification Pattern is a valuable tool for improving code maintainability, flexibility, and readability in .NET Core applications. By encapsulating business rules as separate entities and composing them using composite specifications, developers can create a more modular and extensible code base. When applied appropriately, the Specification Pattern enhances the quality and maintainability of .NET Core projects, ensuring they can easily adapt to changing requirements and business logic.