What is the Specification Design Pattern?

Göksu Deniz
8 min readDec 29, 2022

--

Image is created by DALL-E 2 AI

The specification design pattern is a behavioral design pattern that allows you to create a set of criteria for filtering objects. It is used to separate a set of objects into two groups: those that meet the specified criteria and those that don’t.

In the specification pattern, you define a set of conditions that an object must meet in order to be considered a “valid” object. This set of conditions is known as a specification. You can then use this specification to filter a list of objects, returning only those that meet the specified criteria.

The specification pattern is useful when you want to perform complex filtering operations on a list of objects. It allows you to encapsulate the filtering logic in a separate class, making it easier to reuse and maintain.

Imagine that you have a bunch of toy cars and you want to sort them into two piles: one pile for the red cars and one pile for the blue cars. You could do this by looking at each car one by one and deciding whether it goes in the red pile or the blue pile. But what if you also want to sort the cars by size? And what if you want to be able to sort the cars by other criteria, like whether they are electric or not?

The specification design pattern is a way to make it easier to sort objects like toy cars. It allows you to define a set of rules, called a “specification”, that tells you which objects go in which pile. For example, you could create a specification that says “put all the red cars in the red pile” and another specification that says “put all the small cars in the small pile”. Then, you can use these specifications to sort your toy cars into piles based on different criteria.

To use the specification design pattern, you would first define your specifications by creating a class for each set of rules. Each specification class would have a method that takes an object (like a toy car) as an argument and returns true if the object meets the criteria defined by the specification, and false if it doesn't.

Then, you could create a class that has a method for sorting a list of objects using a given specification. This method would go through the list of objects one by one, and use the specification to decide which pile each object goes in.

Using the specification design pattern makes it easier to sort objects because you can reuse the same sorting method for different criteria. For example, you could use the same method to sort your toy cars by color, size, and type, just by using different specifications. This makes your code more flexible and easier to maintain.

Here is a simple example of the specification design pattern implemented in C#:

public interface ISpecification<T>
{
bool IsSatisfiedBy(T candidate);
}

public class EmployeeSpecification : ISpecification<Employee>
{
private readonly int _minSalary;

public EmployeeSpecification(int minSalary)
{
_minSalary = minSalary;
}

public bool IsSatisfiedBy(Employee candidate)
{
return candidate.Salary >= _minSalary;
}
}

public class EmployeeFilter
{
public List<Employee> Filter(List<Employee> employees, ISpecification<Employee> specification)
{
var results = new List<Employee>();
foreach (var employee in employees)
{
if (specification.IsSatisfiedBy(employee))
{
results.Add(employee);
}
}
return results;
}
}

In this example, the EmployeeSpecification class defines a specification for filtering employees based on their salary. The specification is defined as an interface, ISpecification, with a single method, IsSatisfiedBy, which takes an object as an argument and returns a boolean value indicating whether or not the object meets the specified criteria.

The EmployeeSpecification class implements the ISpecification interface and provides a concrete implementation of the IsSatisfiedBy method, which checks the salary of the given employee to see if it meets the specified criteria.

The EmployeeFilter class provides a method for filtering a list of employees using a given specification. The Filter method takes a list of employees and a specification as arguments, and returns a new list containing only those employees that meet the specified criteria.

You can use the specification design pattern in your own code by creating your own specification classes that define the criteria you want to use to filter a list of objects. Then, you can use an instance of the specification class with the Filter method of the EmployeeFilter class to filter a list of employees (or any other type of object).

The specification design pattern is useful in situations where you want to filter a list of objects based on complex criteria. It allows you to encapsulate the filtering logic in a separate class, making it easier to reuse and maintain.

Here are a few examples of real-world scenarios where the specification design pattern might be used:

  • In an e-commerce application, you might use the specification design pattern to filter a list of products based on various criteria, such as price, availability, and color.
  • In a job matching application, you might use the specification design pattern to filter a list of job postings based on various criteria, such as location, salary, and experience level.
  • In a social networking application, you might use the specification design pattern to filter a list of users based on various criteria, such as age, location, and interests.
  • In a customer relationship management (CRM) system, you might use the specification design pattern to filter a list of customers based on various criteria, such as purchase history, location, and demographics.

In each of these scenarios, the specification design pattern allows you to define a set of criteria for filtering a list of objects and then use that criteria to easily and efficiently filter the list.

Let’s checkout the e-commerce sample together:

public interface ISpecification<T>
{
bool IsSatisfiedBy(T candidate);
}

public class ProductPriceSpecification : ISpecification<Product>
{
private readonly decimal _minPrice;
private readonly decimal _maxPrice;

public ProductPriceSpecification(decimal minPrice, decimal maxPrice)
{
_minPrice = minPrice;
_maxPrice = maxPrice;
}

public bool IsSatisfiedBy(Product candidate)
{
return candidate.Price >= _minPrice && candidate.Price <= _maxPrice;
}
}

public class ProductCategorySpecification : ISpecification<Product>
{
private readonly string _category;

public ProductCategorySpecification(string category)
{
_category = category;
}

public bool IsSatisfiedBy(Product candidate)
{
return candidate.Category == _category;
}
}

public class ProductFilter
{
public List<Product> Filter(List<Product> products, ISpecification<Product> specification)
{
var results = new List<Product>();
foreach (var product in products)
{
if (specification.IsSatisfiedBy(product))
{
results.Add(product);
}
}
return results;
}
}

In this example, the ProductPriceSpecification class defines a specification for filtering products based on their price, and the ProductCategorySpecification class defines a specification for filtering products based on their category. Both specifications implement the ISpecification interface and provide a concrete implementation of the IsSatisfiedBy method, which checks the price or category of the given product to see if it meets the specified criteria.

The ProductFilter class provides a method for filtering a list of products using a given specification. The Filter method takes a list of products and a specification as arguments, and returns a new list containing only those products that meet the specified criteria.

To use the specification design pattern to filter a list of products based on multiple criteria, you could create instances of the ProductPriceSpecification and ProductCategorySpecification classes and pass them both to the Filter method of the ProductFilter class. For example:

List<Product> products = ...;
ProductFilter filter = new ProductFilter();

ISpecification<Product> priceSpec = new ProductPriceSpecification(50, 100);
ISpecification<Product> categorySpec = new ProductCategorySpecification("Electronics");

List<Product> filteredProducts = filter.Filter(products, priceSpec.And(categorySpec));

This would return a list of all products that are both in the “Electronics” category and have a price between 50 and 100.

As you can se, i used And method to combine specifications. In order to use the And method to combine multiple specifications, you will need to define it yourself.

Here is how you could define the And method using an extension method in C#:

public static class SpecificationExtensions
{
public static ISpecification<T> And<T>(this ISpecification<T> left, ISpecification<T> right)
{
return new AndSpecification<T>(left, right);
}
}

public class AndSpecification<T> : ISpecification<T>
{
private readonly ISpecification<T> _left;
private readonly ISpecification<T> _right;

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

public bool IsSatisfiedBy(T candidate)
{
return _left.IsSatisfiedBy(candidate) && _right.IsSatisfiedBy(candidate);
}
}

The AndSpecification class is a implementation of the ISpecification interface that combines two other specifications using the logical AND operator. The And extension method allows you to use the AND operator to combine multiple specifications in a more readable and concise way.

And then, you can use the Add method to combine multiple specifications.

For use the specification design pattern with Entity Framework in C# to filter a list of products in an e-commerce application, you can change the name of IsSatisfiedBy to ToExpression for just naming standards and continue to use it with dbcontext like given below:

public class ProductService
{
private readonly ProductContext _context;

public ProductService(ProductContext context)
{
_context = context;
}

public IEnumerable<Product> Find(ISpecification<Product> specification)
{
return _context.Products.Where(specification.ToExpression()).ToList();
}
}

And you can call it from where you want to use (like Presentation Layer):

var productService = new ProductService();
ISpecification<Product> priceSpecification = new ProductPriceSpecification(50, 100);
ISpecification<Product> categorySpecification = new CategorySpecification("Electronics");

ISpecification<Product> combinedSpecification = priceSpecification.And(categorySpecification);

var filteredProducts = productService.Find(combinedSpecification);

Disadvantages

One potential disadvantage of using the specification design pattern is that it can add additional complexity to your codebase. Defining and implementing specification classes can require more upfront effort and may make your code more difficult to understand for developers who are not familiar with the pattern.

Additionally, the specification design pattern can involve a lot of boilerplate code, as you need to define an interface and implementation for each specification. This can make it more difficult to add new specifications or modify existing ones.

Another potential disadvantage of the specification design pattern is that it can be less efficient than other approaches to filtering lists of objects. For example, if you are using the specification design pattern with Entity Framework, each specification will be translated into an Expression and passed to the Where method, which can be less efficient than writing the filtering logic directly in the Where method.

Conclusion

As a conclusion, the specification design pattern is a useful way to filter a list of objects based on complex criteria. It allows you to encapsulate the filtering logic in a separate class, making it easier to reuse and maintain.

The specification design pattern can be implemented in a variety of programming languages, including C#. To use the pattern, you define a specification class that implements an interface with a method for evaluating whether a given object meets the specified criteria. You can then create a class that provides a method for filtering a list of objects using a given specification.

The specification design pattern can be used in a variety of real-world scenarios, including e-commerce, job matching, social networking, and customer relationship management. It can also be used with various frameworks and libraries, such as Entity Framework, to filter data stored in a database.

Overall, the specification design pattern can be a useful tool for organizing and managing complex filtering logic, but it may not be the best choice in every situation. It is important to consider the trade-offs and determine whether the benefits of using the pattern outweigh the potential disadvantages in your specific use case.

Thanks for reading! If you found the article helpful, you can clap and follow. So you will notified of new articles.

# Reference

It was created with the help of ChatGPT AI.

--

--

Göksu Deniz
Göksu Deniz

Written by Göksu Deniz

Software Engineer, passionate about creating efficient solutions. Skilled in mentoring teams to deliver successful projects. Always exploring new tech trends.

No responses yet