Let's first try to understand the problem we are trying to solve by using Specification Design Pattern.
Imagine you are designing an e-commerce website, the website has multiple items and these items can be filtered based on their size and/or color. Let's first create a class to represent our product/item. The class will have name, size and color as its properties. We can also pre-define color and size by using Enums.
Now one of the features was that you can filter products based on color. So you write a class ProductFilter and implement a method inside of it to filter products based strictly on provided color.
So far so good. Your manager comes in and says we also need to filter products based on size. You say, it's simple and can be done in next few hours. So you go ahead and add a method to your ProductFilter class.
The code is pushed to production and everything works great. One day the client gets back to Manager with a feedback that they also need a filter which can search with Color and Size.
Not a problem you just go ahead and add a method to your productFilter Class once again.
Notice how you are adding more and more methods to the same class. This clearly violates the Open Closed Principle which says that the class should be open for extension but closed for modification. You already implemented an AND filter where you filter items which match both size and color, but I do see a scenario where the client may want an OR filter as well.
We can't really keep this up as this code is difficult to maintain, will require modification every time and will be difficult to scale.
What is Specification Design Pattern ?
Specification Design Pattern helps us to check if our objects satisfy certain criteria. Through this design pattern we can create new specifications and combine them as we need and check if our objects satisfy those specifications.
You will get a better sense of this explanation once we write some code. Let's try to refactor our previous code. First let's write a Specification class. This class will determine if a particular item satisfies a certain criteria.
Specification class is just a base class and the method 'is_satisfied' is supposed to be overwritten by the class who inherits it. In our design we have 2 specifications so far, which are color and size.
We extend the base class and create ColorSpecification and SizeSpecification class. Both these classes overwrite the base class method, and perform a check if the specification is satisfied.
You initialize ColorSpecification class with a specific color (RED, GREEN or BLUE). The method 'is_satisfied' accepts a product and verifies if the specification color and provided product's color match and returns a boolean value.
We also need to implement another class called as Filter Class, which again will be a base class. The idea behind filter class is to implement the filter methods we wrote previously but following the Open-Closed-Principle.
BetterFilter class extends Filter class and overwrites the method filter. The filter method accepts products/items and a specification. It loops though the items and uses Specification class to make sure that the item satisfies the Specification.
As you can see we do not explicitly mention the Specification (color, size) here, so even in future if we add more Specification like weight, manufacturer etc, this filter class won't change.
These classes may seem a bit cryptic at the moment. Let's talk about how we can use them.
We instantiate objects for ColorSpecification and SizeSpecification class. Now by passing these to object of BetterFilter class, we can filter items form our store without explicitly mentioning any specific size or color.
We can add more specifications like weight, source etc. by extending the base Specification class and filter them without any modifications to existing classes.
Now, one of things we did not implement yet is the combination of Specifications. What I mean by that is, we might need a filter which might be able to filter for a combination. For example we want to filter items which are green and small, or filter items which are red and small.
We can achieve this by adding a new Specification called as AndSpecification.
We can define AndSpecification and it can accept any number of specifications to check. We have used an initializer with *args which can be any number of arguments. So, technically we can filter items on size, color and weight (if we had weight specification).
Here is how you can use AndSpecification class.
When to use Specification Design Pattern
Specification Design Pattern can definitely add some complexity to your code but, it can also make your code more manageable. scalable and easy to read. For example let's assume you have a user and you need to check if the user is an adult, if user is married, if user has a child and so on. If you prepare your class to answer all these questions, then you will end up with a huge class. Also, there might be few checks that you might add later on. This will result in one big chunk of file which you and other developers will end up maintaining. Instead if you use Specification design pattern you can prevent this.
Hope this was useful and gave you a general idea on how this design pattern can be useful.