Specification pattern

Last updated
Specification Pattern in UML Specification UML.png
Specification Pattern in UML

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.

Contents

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.

As a consequence of performing runtime composition of high-level business/domain logic, the Specification pattern is a convenient tool for converting ad-hoc user search criteria into low level logic to be processed by repositories.

Since a specification is an encapsulation of logic in a reusable form it is very simple to thoroughly unit test, and when used in this context is also an implementation of the humble object pattern.

Code examples

C#

publicinterfaceISpecification{boolIsSatisfiedBy(objectcandidate);ISpecificationAnd(ISpecificationother);ISpecificationAndNot(ISpecificationother);ISpecificationOr(ISpecificationother);ISpecificationOrNot(ISpecificationother);ISpecificationNot();}publicabstractclassCompositeSpecification:ISpecification{publicabstractboolIsSatisfiedBy(objectcandidate);publicISpecificationAnd(ISpecificationother){returnnewAndSpecification(this,other);}publicISpecificationAndNot(ISpecificationother){returnnewAndNotSpecification(this,other);}publicISpecificationOr(ISpecificationother){returnnewOrSpecification(this,other);}publicISpecificationOrNot(ISpecificationother){returnnewOrNotSpecification(this,other);}publicISpecificationNot(){returnnewNotSpecification(this);}}publicclassAndSpecification:CompositeSpecification{privateISpecificationleftCondition;privateISpecificationrightCondition;publicAndSpecification(ISpecificationleft,ISpecificationright){leftCondition=left;rightCondition=right;}publicoverrideboolIsSatisfiedBy(objectcandidate){returnleftCondition.IsSatisfiedBy(candidate)&&rightCondition.IsSatisfiedBy(candidate);}}publicclassAndNotSpecification:CompositeSpecification{privateISpecificationleftCondition;privateISpecificationrightCondition;publicAndNotSpecification(ISpecificationleft,ISpecificationright){leftCondition=left;rightCondition=right;}publicoverrideboolIsSatisfiedBy(objectcandidate){returnleftCondition.IsSatisfiedBy(candidate)&&rightCondition.IsSatisfiedBy(candidate)!=true;}}publicclassOrSpecification:CompositeSpecification{privateISpecificationleftCondition;privateISpecificationrightCondition;publicOrSpecification(ISpecificationleft,ISpecificationright){leftCondition=left;rightCondition=right;}publicoverrideboolIsSatisfiedBy(objectcandidate){returnleftCondition.IsSatisfiedBy(candidate)||rightCondition.IsSatisfiedBy(candidate);}}publicclassOrNotSpecification:CompositeSpecification{privateISpecificationleftCondition;privateISpecificationrightCondition;publicOrNotSpecification(ISpecificationleft,ISpecificationright){leftCondition=left;rightCondition=right;}publicoverrideboolIsSatisfiedBy(objectcandidate){returnleftCondition.IsSatisfiedBy(candidate)||rightCondition.IsSatisfiedBy(candidate)!=true;}}publicclassNotSpecification:CompositeSpecification{privateISpecificationWrapped;publicNotSpecification(ISpecificationx){Wrapped=x;}publicoverrideboolIsSatisfiedBy(objectcandidate){return!Wrapped.IsSatisfiedBy(candidate);}}

C# 6.0 with generics

publicinterfaceISpecification<T>{boolIsSatisfiedBy(Tcandidate);ISpecification<T>And(ISpecification<T>other);ISpecification<T>AndNot(ISpecification<T>other);ISpecification<T>Or(ISpecification<T>other);ISpecification<T>OrNot(ISpecification<T>other);ISpecification<T>Not();}publicabstractclassLinqSpecification<T>:CompositeSpecification<T>{publicabstractExpression<Func<T,bool>>AsExpression();publicoverrideboolIsSatisfiedBy(Tcandidate)=>AsExpression().Compile()(candidate);}publicabstractclassCompositeSpecification<T>:ISpecification<T>{publicabstractboolIsSatisfiedBy(Tcandidate);publicISpecification<T>And(ISpecification<T>other)=>newAndSpecification<T>(this,other);publicISpecification<T>AndNot(ISpecification<T>other)=>newAndNotSpecification<T>(this,other);publicISpecification<T>Or(ISpecification<T>other)=>newOrSpecification<T>(this,other);publicISpecification<T>OrNot(ISpecification<T>other)=>newOrNotSpecification<T>(this,other);publicISpecification<T>Not()=>newNotSpecification<T>(this);}publicclassAndSpecification<T>:CompositeSpecification<T>{ISpecification<T>left;ISpecification<T>right;publicAndSpecification(ISpecification<T>left,ISpecification<T>right){this.left=left;this.right=right;}publicoverrideboolIsSatisfiedBy(Tcandidate)=>left.IsSatisfiedBy(candidate)&&right.IsSatisfiedBy(candidate);}publicclassAndNotSpecification<T>:CompositeSpecification<T>{ISpecification<T>left;ISpecification<T>right;publicAndNotSpecification(ISpecification<T>left,ISpecification<T>right){this.left=left;this.right=right;}publicoverrideboolIsSatisfiedBy(Tcandidate)=>left.IsSatisfiedBy(candidate)&&!right.IsSatisfiedBy(candidate);}publicclassOrSpecification<T>:CompositeSpecification<T>{ISpecification<T>left;ISpecification<T>right;publicOrSpecification(ISpecification<T>left,ISpecification<T>right){this.left=left;this.right=right;}publicoverrideboolIsSatisfiedBy(Tcandidate)=>left.IsSatisfiedBy(candidate)||right.IsSatisfiedBy(candidate);}publicclassOrNotSpecification<T>:CompositeSpecification<T>{ISpecification<T>left;ISpecification<T>right;publicOrNotSpecification(ISpecification<T>left,ISpecification<T>right){this.left=left;this.right=right;}publicoverrideboolIsSatisfiedBy(Tcandidate)=>left.IsSatisfiedBy(candidate)||!right.IsSatisfiedBy(candidate);}publicclassNotSpecification<T>:CompositeSpecification<T>{ISpecification<T>other;publicNotSpecification(ISpecification<T>other)=>this.other=other;publicoverrideboolIsSatisfiedBy(Tcandidate)=>!other.IsSatisfiedBy(candidate);}

Python

fromabcimportABC,abstractmethodfromdataclassesimportdataclassfromtypingimportAnyclassBaseSpecification(ABC):@abstractmethoddefis_satisfied_by(self,candidate:Any)->bool:raiseNotImplementedError()def__call__(self,candidate:Any)->bool:returnself.is_satisfied_by(candidate)def__and__(self,other:"BaseSpecification")->"AndSpecification":returnAndSpecification(self,other)def__or__(self,other:"BaseSpecification")->"OrSpecification":returnOrSpecification(self,other)def__neg__(self)->"NotSpecification":returnNotSpecification(self)@dataclass(frozen=True)classAndSpecification(BaseSpecification):first:BaseSpecificationsecond:BaseSpecificationdefis_satisfied_by(self,candidate:Any)->bool:returnself.first.is_satisfied_by(candidate)andself.second.is_satisfied_by(candidate)@dataclass(frozen=True)classOrSpecification(BaseSpecification):first:BaseSpecificationsecond:BaseSpecificationdefis_satisfied_by(self,candidate:Any)->bool:returnself.first.is_satisfied_by(candidate)orself.second.is_satisfied_by(candidate)@dataclass(frozen=True)classNotSpecification(BaseSpecification):subject:BaseSpecificationdefis_satisfied_by(self,candidate:Any)->bool:returnnotself.subject.is_satisfied_by(candidate)

C++

template<classT>classISpecification{public:virtual~ISpecification()=default;virtualboolIsSatisfiedBy(TCandidate)const=0;virtualISpecification<T>*And(constISpecification<T>&Other)const=0;virtualISpecification<T>*AndNot(constISpecification<T>&Other)const=0;virtualISpecification<T>*Or(constISpecification<T>&Other)const=0;virtualISpecification<T>*OrNot(constISpecification<T>&Other)const=0;virtualISpecification<T>*Not()const=0;};template<classT>classCompositeSpecification:publicISpecification<T>{public:virtualboolIsSatisfiedBy(TCandidate)constoverride=0;virtualISpecification<T>*And(constISpecification<T>&Other)constoverride;virtualISpecification<T>*AndNot(constISpecification<T>&Other)constoverride;virtualISpecification<T>*Or(constISpecification<T>&Other)constoverride;virtualISpecification<T>*OrNot(constISpecification<T>&Other)constoverride;virtualISpecification<T>*Not()constoverride;};template<classT>classAndSpecificationfinal:publicCompositeSpecification<T>{public:constISpecification<T>&Left;constISpecification<T>&Right;AndSpecification(constISpecification<T>&InLeft,constISpecification<T>&InRight):Left(InLeft),Right(InRight){}virtualboolIsSatisfiedBy(TCandidate)constoverride{returnLeft.IsSatisfiedBy(Candidate)&&Right.IsSatisfiedBy(Candidate);}};template<classT>ISpecification<T>*CompositeSpecification<T>::And(constISpecification<T>&Other)const{returnnewAndSpecification<T>(*this,Other);}template<classT>classAndNotSpecificationfinal:publicCompositeSpecification<T>{public:constISpecification<T>&Left;constISpecification<T>&Right;AndNotSpecification(constISpecification<T>&InLeft,constISpecification<T>&InRight):Left(InLeft),Right(InRight){}virtualboolIsSatisfiedBy(TCandidate)constoverride{returnLeft.IsSatisfiedBy(Candidate)&&!Right.IsSatisfiedBy(Candidate);}};template<classT>classOrSpecificationfinal:publicCompositeSpecification<T>{public:constISpecification<T>&Left;constISpecification<T>&Right;OrSpecification(constISpecification<T>&InLeft,constISpecification<T>&InRight):Left(InLeft),Right(InRight){}virtualboolIsSatisfiedBy(TCandidate)constoverride{returnLeft.IsSatisfiedBy(Candidate)||Right.IsSatisfiedBy(Candidate);}};template<classT>classOrNotSpecificationfinal:publicCompositeSpecification<T>{public:constISpecification<T>&Left;constISpecification<T>&Right;OrNotSpecification(constISpecification<T>&InLeft,constISpecification<T>&InRight):Left(InLeft),Right(InRight){}virtualboolIsSatisfiedBy(TCandidate)constoverride{returnLeft.IsSatisfiedBy(Candidate)||!Right.IsSatisfiedBy(Candidate);}};template<classT>classNotSpecificationfinal:publicCompositeSpecification<T>{public:constISpecification<T>&Other;NotSpecification(constISpecification<T>&InOther):Other(InOther){}virtualboolIsSatisfiedBy(TCandidate)constoverride{return!Other.IsSatisfiedBy(Candidate);}};template<classT>ISpecification<T>*CompositeSpecification<T>::AndNot(constISpecification<T>&Other)const{returnnewAndNotSpecification<T>(*this,Other);}template<classT>ISpecification<T>*CompositeSpecification<T>::Or(constISpecification<T>&Other)const{returnnewOrSpecification<T>(*this,Other);}template<classT>ISpecification<T>*CompositeSpecification<T>::OrNot(constISpecification<T>&Other)const{returnnewOrNotSpecification<T>(*this,Other);}template<classT>ISpecification<T>*CompositeSpecification<T>::Not()const{returnnewNotSpecification<T>(*this);}

TypeScript

exportinterfaceISpecification{isSatisfiedBy(candidate:unknown):boolean;and(other:ISpecification):ISpecification;andNot(other:ISpecification):ISpecification;or(other:ISpecification):ISpecification;orNot(other:ISpecification):ISpecification;not():ISpecification;}exportabstractclassCompositeSpecificationimplementsISpecification{abstractisSatisfiedBy(candidate:unknown):boolean;and(other:ISpecification):ISpecification{returnnewAndSpecification(this,other);}andNot(other:ISpecification):ISpecification{returnnewAndNotSpecification(this,other);}or(other:ISpecification):ISpecification{returnnewOrSpecification(this,other);}orNot(other:ISpecification):ISpecification{returnnewOrNotSpecification(this,other);}not():ISpecification{returnnewNotSpecification(this);}}exportclassAndSpecificationextendsCompositeSpecification{constructor(privateleftCondition:ISpecification,privaterightCondition:ISpecification){super();}isSatisfiedBy(candidate:unknown):boolean{returnthis.leftCondition.isSatisfiedBy(candidate)&&this.rightCondition.isSatisfiedBy(candidate);}}exportclassAndNotSpecificationextendsCompositeSpecification{constructor(privateleftCondition:ISpecification,privaterightCondition:ISpecification){super();}isSatisfiedBy(candidate:unknown):boolean{returnthis.leftCondition.isSatisfiedBy(candidate)&&this.rightCondition.isSatisfiedBy(candidate)!==true;}}exportclassOrSpecificationextendsCompositeSpecification{constructor(privateleftCondition:ISpecification,privaterightCondition:ISpecification){super();}isSatisfiedBy(candidate:unknown):boolean{returnthis.leftCondition.isSatisfiedBy(candidate)||this.rightCondition.isSatisfiedBy(candidate);}}exportclassOrNotSpecificationextendsCompositeSpecification{constructor(privateleftCondition:ISpecification,privaterightCondition:ISpecification){super();}isSatisfiedBy(candidate:unknown):boolean{returnthis.leftCondition.isSatisfiedBy(candidate)||this.rightCondition.isSatisfiedBy(candidate)!==true;}}exportclassNotSpecificationextendsCompositeSpecification{constructor(privatewrapped:ISpecification){super();}isSatisfiedBy(candidate:unknown):boolean{return!this.wrapped.isSatisfiedBy(candidate);}}

Example of use

In the next example, invoices are retrieved and sent 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 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.

varoverDue=newOverDueSpecification();varnoticeSent=newNoticeSentSpecification();varinCollection=newInCollectionSpecification();// Example of specification pattern logic chainingvarsendToCollection=overDue.And(noticeSent).And(inCollection.Not());varinvoiceCollection=Service.GetInvoices();foreach(varcurrentInvoiceininvoiceCollection){if(sendToCollection.IsSatisfiedBy(currentInvoice)){currentInvoice.SendToCollection();}}

Related Research Articles

A visitor pattern is a software design pattern that separates the algorithm from the object structure. Because of this separation, new operations can be added to existing object structures without modifying the structures. It is one way to follow the open/closed principle in object-oriented programming and software engineering.

In software engineering, the adapter pattern is a software design pattern that allows the interface of an existing class to be used as another interface. It is often used to make existing classes work with others without modifying their source code.

The bridge pattern is a design pattern used in software engineering that is meant to "decouple an abstraction from its implementation so that the two can vary independently", introduced by the Gang of Four. The bridge uses encapsulation, aggregation, and can use inheritance to separate responsibilities into different classes.

In object oriented programming, the factory method pattern is a design pattern that uses factory methods to deal with the problem of creating objects without having to specify their exact class. Rather than by calling a constructor, this is done by calling a factory method to create an object. Factory methods can either be specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes. It is one of the 23 classic design patterns described in the book Design Patterns and is sub-categorized as a creational pattern.

In software engineering, the composite pattern is a partitioning design pattern. The composite pattern describes a group of objects that are treated the same way as a single instance of the same type of object. The intent of a composite is to "compose" objects into tree structures to represent part-whole hierarchies. Implementing the composite pattern lets clients treat individual objects and compositions uniformly.

In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other instances of the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern as well as to the Open-Closed Principle, by allowing the functionality of a class to be extended without being modified. Decorator use can be more efficient than subclassing, because an object's behavior can be augmented without defining an entirely new object.

In computer programming, the interpreter pattern is a design pattern that specifies how to evaluate sentences in a language. The basic idea is to have a class for each symbol in a specialized computer language. The syntax tree of a sentence in the language is an instance of the composite pattern and is used to evaluate (interpret) the sentence for a client. See also Composite pattern.

In software design and engineering, the observer pattern is a software design pattern in which an object, named the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods.

Multiple dispatch or multimethods is a feature of some programming languages in which a function or method can be dynamically dispatched based on the run-time (dynamic) type or, in the more general case, some other attribute of more than one of its arguments. This is a generalization of single-dispatch polymorphism where a function or method call is dynamically dispatched based on the derived type of the object on which the method has been called. Multiple dispatch routes the dynamic dispatch to the implementing function or method using the combined characteristics of one or more arguments.

In object-oriented (OO) and functional programming, an immutable object is an object whose state cannot be modified after it is created. This is in contrast to a mutable object, which can be modified after it is created. In some cases, an object is considered immutable even if some internally used attributes change, but the object's state appears unchanging from an external point of view. For example, an object that uses memoization to cache the results of expensive computations could still be considered an immutable object.

In knowledge representation and ontology components, including for object-oriented programming and design, is-a is a subsumptive relationship between abstractions, wherein one class A is a subclass of another class B . In other words, type A is a subtype of type B when A's specification implies B's specification. That is, any object that satisfies A's specification also satisfies B's specification, because B's specification is weaker.

In computer programming, a function object is a construct allowing an object to be invoked or called as if it were an ordinary function, usually with the same syntax. In some languages, particularly C++, function objects are often called functors.

Short-circuit evaluation, minimal evaluation, or McCarthy evaluation is the semantics of some Boolean operators in some programming languages in which the second argument is executed or evaluated only if the first argument does not suffice to determine the value of the expression: when the first argument of the AND function evaluates to false, the overall value must be false; and when the first argument of the OR function evaluates to true, the overall value must be true.

<span class="mw-page-title-main">Method overriding</span> Language feature in object-oriented programming

Method overriding, in object-oriented programming, is a language feature that allows a subclass or child class to provide a specific implementation of a method that is already provided by one of its superclasses or parent classes. In addition to providing data-driven algorithm-determined parameters across virtual network interfaces, it also allows for a specific type of polymorphism (subtyping). The implementation in the subclass overrides (replaces) the implementation in the superclass by providing a method that has same name, same parameters or signature, and same return type as the method in the parent class. The version of a method that is executed will be determined by the object that is used to invoke it. If an object of a parent class is used to invoke the method, then the version in the parent class will be executed, but if an object of the subclass is used to invoke the method, then the version in the child class will be executed. This helps in preventing problems associated with differential relay analytics which would otherwise rely on a framework in which method overriding might be obviated. Some languages allow a programmer to prevent a method from being overridden.

The curiously recurring template pattern (CRTP) is an idiom, originally in C++, in which a class X derives from a class template instantiation using X itself as a template argument. More generally it is known as F-bound polymorphism, and it is a form of F-bounded quantification.

C++11 is a version of the ISO/IEC 14882 standard for the C++ programming language. C++11 replaced the prior version of the C++ standard, called C++03, and was later replaced by C++14. The name follows the tradition of naming language versions by the publication year of the specification, though it was formerly named C++0x because it was expected to be published before 2010.

A property, in some object-oriented programming languages, is a special sort of class member, intermediate in functionality between a field and a method. The syntax for reading and writing of properties is like for fields, but property reads and writes are (usually) translated to 'getter' and 'setter' method calls. The field-like syntax is easier to read and write than many method calls, yet the interposition of method calls "under the hood" allows for data validation, active updating, or implementation of what may be called "read-only fields".

In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral (null) behavior. The null object design pattern, which describes the uses of such objects and their behavior, was first published as "Void Value" and later in the Pattern Languages of Program Design book series as "Null Object".

Expression templates are a C++ template metaprogramming technique that builds structures representing a computation at compile time, where expressions are evaluated only as needed to produce efficient code for the entire computation. Expression templates thus allow programmers to bypass the normal order of evaluation of the C++ language and achieve optimizations such as loop fusion.

Concepts are an extension to the templates feature provided by the C++ programming language. Concepts are named Boolean predicates on template parameters, evaluated at compile time. A concept may be associated with a template, in which case it serves as a constraint: it limits the set of arguments that are accepted as template parameters.

References