Specification pattern


In computer programming, the specification pattern is a particular software design pattern, whereby business rules can be recombined by chaining the business rules together using boolean logic. The pattern is frequently used in the context of domain-driven design.
A specification pattern outlines a business rule that is combinable with other business rules. In this pattern, a unit of business logic inherits its functionality from the abstract aggregate Composite Specification class. The Composite Specification class has one function called IsSatisfiedBy that returns a boolean value. After instantiation, the specification is "chained" with other specifications, making new specifications easily maintainable, yet highly customizable business logic. Furthermore, upon instantiation the business logic may, through method invocation or inversion of control, have its state altered in order to become a delegate of other classes such as a persistence repository.

Code examples

C#">C Sharp (programming language)">C#


public interface ISpecification

public abstract class CompositeSpecification : ISpecification

public class AndSpecification : CompositeSpecification

public class AndNotSpecification : CompositeSpecification

public class OrSpecification : CompositeSpecification

public class OrNotSpecification : CompositeSpecification

public class NotSpecification : CompositeSpecification

C# 6.0">C Sharp (programming language)">C# 6.0 with generics


public interface ISpecification

public abstract class LinqSpecification : CompositeSpecification

public abstract class CompositeSpecification : ISpecification

public class AndSpecification : CompositeSpecification

public class AndNotSpecification : CompositeSpecification

public class OrSpecification : CompositeSpecification

public class OrNotSpecification : CompositeSpecification

public class NotSpecification : CompositeSpecification

Python">Python (programming language)">Python


from abc import abstractmethod
from dataclasses import dataclass
from typing import Any
class BaseSpecification:
@abstractmethod
def is_satisfied_by -> bool:
raise NotImplementedError
def and_ -> "AndSpecification":
return AndSpecification
def or_ -> "OrSpecification":
return OrSpecification
def not_ -> "NotSpecification":
return NotSpecification
@dataclass
class AndSpecification:
first: BaseSpecification
second: BaseSpecification
def is_satisfied_by -> bool:
return self.first.is_satisfied_by and self.second.is_satisfied_by
@dataclass
class OrSpecification:
first: BaseSpecification
second: BaseSpecification
def is_satisfied_by -> bool:
return self.first.is_satisfied_by or self.second.is_satisfied_by
@dataclass
class NotSpecification:
subject: BaseSpecification
def is_satisfied_by -> bool:
return not self.subject.is_satisfied_by

Example of use

In the following example, we are retrieving invoices and sending them to a collection agency if
  1. they are overdue,
  2. notices have been sent, and
  3. they are not already with the collection agency.
This example is meant to show the end result of how the logic is 'chained' together.
This usage example assumes a previously defined OverdueSpecification class that is satisfied when an invoice's due date is 30 days or older, a NoticeSentSpecification class that is satisfied when three notices have been sent to the customer, and an InCollectionSpecification class that is satisfied when an invoice has already been sent to the collection agency. The implementation of these classes isn't important here.
Using these three specifications, we created a new specification called SendToCollection which will be satisfied when an invoice is overdue, when notices have been sent to the customer, and are not already with the collection agency.

var OverDue = new OverDueSpecification;
var NoticeSent = new NoticeSentSpecification;
var InCollection = new InCollectionSpecification;
// example of specification pattern logic chaining
var SendToCollection = OverDue.And.And);
var InvoiceCollection = Service.GetInvoices;
foreach

Criticisms

The Specification Pattern could be considered a software anti-pattern:
Most natural programming languages can accommodate domain-driven design with the core object-oriented concepts.
Alternative example, without the Specification Pattern:

var InvoiceCollection = Service.GetInvoices;
foreach invoice.SendToCollectionIfNecessary;
// Invoice methods:
public void SendToCollectionIfNecessary
private bool ShouldSendToCollection => currentInvoice.OverDue && currentInvoice.NoticeSent && !currentInvoice.InCollection;

This alternative uses foundation concepts of get-only properties, condition logic, and functions. The key alternative here is Get-Only Properties, which are well-named to maintain the domain-driven language, and enable the continued use of the natural && operator, instead of the Specification Pattern's And function. Furthermore, the creation of a well-named function SendToCollectionIfNecessary is potentially more useful and descriptive, than the previous example.