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 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.

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

In object-oriented programming and software engineering, the visitor design pattern is a way of separating an algorithm from an object structure on which it operates. A practical result of this separation is the ability to add new operations to existing object structures without modifying the structures. It is one way to follow the open/closed principle.

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 class-based programming, the factory method pattern is a creational pattern that uses factory methods to deal with the problem of creating objects without having to specify the exact class of the object that will be created. This is done by creating objects by calling a factory method—either specified in an interface and implemented by child classes, or implemented in a base class and optionally overridden by derived classes—rather than by calling a constructor.

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 objects from 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 proxy pattern is a software design pattern. A proxy, in its most general form, is a class functioning as an interface to something else. The proxy could interface to anything: a network connection, a large object in memory, a file, or some other resource that is expensive or impossible to duplicate. In short, a proxy is a wrapper or agent object that is being called by the client to access the real serving object behind the scenes. Use of the proxy can simply be forwarding to the real object, or can provide additional logic. In the proxy, extra functionality can be provided, for example caching when operations on the real object are resource intensive, or checking preconditions before operations on the real object are invoked. For the client, usage of a proxy object is similar to using the real object, because both implement the same interface.

In object-oriented programming, the iterator pattern is a design pattern in which an iterator is used to traverse a container and access the container's elements. The iterator pattern decouples algorithms from containers; in some cases, algorithms are necessarily container-specific and thus cannot be decoupled.

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 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 subsumption 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. Function objects are often called functors.

<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.

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.

utility is a header file in the C++ Standard Library. This file has two key components:

References