Specification pattern
Specification pattern is a is a software design pattern frequently used in Domain-Driven Design1. It is used to codify business rules that state something about an object. The Specification Pattern is a way of encapsulating business rules to return a boolean value. By encapsulating a business rule within a Specification object, we can create a class that has a single responsibility, but can be combined with other Specification objects to create maintainable rules that can be combined to satisfy complex requirements.
Eric Even and Martin Fowler paper
The central idea of Specification is to separate the statement of how to match a candidate, from the candidate object that it is matched against. As well as its usefulness in selection, it is also valuable for validation and for building to order.2
Wikipedia definition
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.3
Specification pattern and building blocks
Use a specification to encapsulate a business rule which does not belong inside entities or value objects, but is applied to them. Use them to:
- Make assertions (validation) about an object.
- Fetch objects matching certain criteria from a collection (selection).
- Specify how an object should be created (building to order).
Specification pattern advantages
- The Specification pattern is powerful because it allows you to encapsulate the business rule inside of the class whilst providing an easy to use API that can be consumed within your application.
- By using the Specification pattern, you can use the Specification Object anywhere that is necessary in your application. Your application does not need to know how the business rule is enforced because it is internal to the Specification object. If the business rules is changed, you only have to change that single source of truth.
- Using the Specification pattern also makes having alternative, or multiple rules easier to work with. By encapsulating each rule as a Specification object you are free to use many instances together to satisfy the complex requirements of the organisation without getting bogged down in complex or unmaintainable code.
Specification pattern Java Implementation Example
- Each of our rules will have at its disposal an And _method, an Or method and a Not method allows it to be combined with others. The isSatisfiedBy _method must be implemented and validate the rule.
interface SpecificationInterface {
/**
* @param mixed $object
*/
public function isSatisfiedBy($object);
/**
* @param SpecificationInterface $specification
*/
public function andSpec(SpecificationInterface $specification);
/**
* @param SpecificationInterface $specification
*/
public function orSpec(SpecificationInterface $specification);
/**
* @param SpecificationInterface $specification
*/
public function notSpec(SpecificationInterface $specification);
}
- A generic abstract class for our Specifications, it will perform the three logical operations.
abstract class AbstractSpecification implements SpecificationInterface {
public function andSpec(SpecificationInterface $specification) {
return new AndSpecification($this, $specification);
}
public function orSpec(SpecificationInterface $specification) {
return new OrSpecification($this, $specification);
}
public function notSpec(SpecificationInterface $specification) {
return new NotSpecification($this);
}
}
class AndSpecification extends AbstractSpecification implements SpecificationInterface {
private $specification1;
private $specification2;
public function __construct(SpecificationInterface $specification1, SpecificationInterface $specification2) {
$this->specification1 = $specification1;
$this->specification2 = $specification2;
}
public function isSatisfiedBy($object) {
return $this->specification1->isSatisfiedBy($object)
&& $this->specification2->isSatisfiedBy($object);
}
}
class OrSpecification extends AbstractSpecification implements SpecificationInterface {
private $specification1;
private $specification2;
public function __construct(SpecificationInterface $specification1, SpecificationInterface $specification2) {
$this->specification1 = $specification1;
$this->specification2 = $specification2;
}
public function isSatisfiedBy($object) {
return $this->specification1->isSatisfiedBy($object)
|| $this->specification2->isSatisfiedBy($object);
}
}
class NotSpecification extends AbstractSpecification implements SpecificationInterface {
private $specification;
public function __construct(SpecificationInterface $specification) {
$this->specification = $specification;
}
public function isSatisfiedBy($object) {
return !$this->specification->isSatisfiedBy($object);
}
}
- Example of rule
class ValidCreditSpecification extends AbstractSpecification {
/**
* @param mixed $data
* @return bool
*/
public function isSatisfiedBy($data) {
if (!empty($data) && is_array($data)) {
if (isset($data['fundingStatus']) && isset($data['fundingStatus'])) {
if ($data['type'] == 'internal' && $data['fundingStatus'] != 'refused') {
return true;
}
}
}
return false;
}
}
class AcceptedCreditSpecification extends AbstractSpecification {
/**
* @param mixed $data
* @return bool
*/
public function isSatisfiedBy($data) {
if (!empty($data) && is_array($data)) {
if (isset($data['fundingStatus'] )) {
if ($data['fundingStatus'] == 'finance' || $data['fundingStatus'] == 'accepted_with_signature') {
return true;
}
}
}
return false;
}
}
class CreditSpecification {
protected $creditData;
/**
* CreditSpecification constructor.
* @param array $creditData
*/
public function __construct(array $creditData) {
$this->creditData = $creditData;
}
/**
* @return bool
*/
public function isValid() {
$acceptedCreditSpecification = new ValidCreditSpecification();
return $acceptedCreditSpecification->isSatisfiedBy($this->creditData);
}
/**
* @return bool
*/
public function isAccepted() {
$acceptedCreditSpecification = new AcceptedCreditSpecification();
$acceptedCreditSpecification->andSpecification(new ValidCreditSpecification());
return $acceptedCreditSpecification->isSatisfiedBy($this->creditData);
}
}
$creditSpecification = new CreditSpecification($data);
if ($creditSpecification->isValid()){
//action
} elseif ($creditSpecification->isAccepted()){
//action
}
1. “Domain-driven Design”, This content ↩
2. “Specifications”, Martin Fowler and Eric Even, https://martinfowler.com/apsupp/spec.pdf ↩
3. “Specification pattern”, Wikipedia, https://en.wikipedia.org/wiki/Specification_pattern ↩