Content deleted Content added
GreenC bot (talk | contribs) |
m corrected compilation error |
||
(40 intermediate revisions by 26 users not shown) | |||
Line 1:
{{Short description|Software design pattern}}
[[File:Specification UML.png|right|thumb|300px|Specification Pattern in [[Unified Modeling Language|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 algebra|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.
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 ==
===
{{Further|C Sharp (programming language)}}
<syntaxhighlight lang="csharp">
public interface ISpecification
{
bool IsSatisfiedBy(object candidate);
ISpecification And(ISpecification other);
ISpecification AndNot(ISpecification other);
ISpecification Or(ISpecification other);
ISpecification OrNot(ISpecification other);
ISpecification Not();
}
public abstract class CompositeSpecification : ISpecification
{
public abstract bool IsSatisfiedBy(object candidate);
public ISpecification And(ISpecification other)
{
}
public ISpecification AndNot(ISpecification other)
{
}
}
public class AndSpecification : CompositeSpecification
{
private ISpecification _leftCondition;
private ISpecification _rightCondition;
public AndSpecification(ISpecification
_leftCondition = left;
}
public override bool IsSatisfiedBy(object candidate)
{
return _leftCondition.IsSatisfiedBy(candidate) && _rightCondition.IsSatisfiedBy(candidate);
}
}
public class AndNotSpecification : CompositeSpecification
{
private ISpecification _leftCondition;
private ISpecification _rightCondition;
public AndNotSpecification(ISpecification left, ISpecification right)
_leftCondition = left;
}
public override bool IsSatisfiedBy(object candidate)
{
return _leftCondition.IsSatisfiedBy(candidate) && _rightCondition.IsSatisfiedBy(candidate) != true;
}
}
public class OrSpecification : CompositeSpecification
{
private ISpecification _leftCondition;
private ISpecification _rightCondition;
public OrSpecification(ISpecification left, ISpecification right)
_leftCondition = left;
}
public override bool IsSatisfiedBy(object candidate)
{
return _leftCondition.IsSatisfiedBy(candidate) || _rightCondition.IsSatisfiedBy(candidate);
}
}
public class OrNotSpecification : CompositeSpecification
{
private ISpecification _leftCondition;
private ISpecification _rightCondition;
public OrNotSpecification(ISpecification left, ISpecification right)
_leftCondition = left;
}
public override bool IsSatisfiedBy(object candidate)
{
return _leftCondition.IsSatisfiedBy(candidate) || _rightCondition.IsSatisfiedBy(candidate) != true;
}
}
public class NotSpecification : CompositeSpecification
{
private ISpecification _wrapped;
_wrapped = x;
}
public override bool IsSatisfiedBy(object candidate)
{
return !_wrapped.IsSatisfiedBy(candidate);
}
}
</syntaxhighlight>
=== C# 6.0 with generics ===
{{Further|C Sharp (programming language)}}
<syntaxhighlight lang="csharp">
public interface ISpecification<T>
{
bool IsSatisfiedBy(T candidate);
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();
}
public abstract class LinqSpecification<T> : CompositeSpecification<T>
{
public abstract Expression<Func<T, bool>> AsExpression();
public override bool IsSatisfiedBy(T candidate) => AsExpression().Compile()(candidate);
}
public abstract class CompositeSpecification<T> : ISpecification<T>
{
public abstract bool IsSatisfiedBy(T candidate);
public ISpecification<T> And(ISpecification<T> other) => new AndSpecification<T>(this, other);
public ISpecification<T> AndNot(ISpecification<T> other) => new AndNotSpecification<T>(this, other);
public ISpecification<T> Or(ISpecification<T> other) => new OrSpecification<T>(this, other);
public ISpecification<T> OrNot(ISpecification<T> other) => new OrNotSpecification<T>(this, other);
public ISpecification<T> Not() => new NotSpecification<T>(this);
}
public class AndSpecification<T> : CompositeSpecification<T>
{
private ISpecification<T> _left;
private ISpecification<T> _right;
public AndSpecification(ISpecification<T> left, ISpecification<T> right)
{
_right = right;
}
public override bool IsSatisfiedBy(T candidate) => _left.IsSatisfiedBy(candidate) && _right.IsSatisfiedBy(candidate);
}
public class AndNotSpecification<T> : CompositeSpecification<T>
{
private ISpecification<T> _left;
private ISpecification<T> _right;
public AndNotSpecification(ISpecification<T> left, ISpecification<T> right)
{
_left = left;
_right = right;
}
}
public class OrSpecification<T> : CompositeSpecification<T>
{
private ISpecification<T> _left;
private ISpecification<T> _right;
public OrSpecification(ISpecification<T> left, ISpecification<T> right)
{
_left = left;
_right = right;
}
public override bool IsSatisfiedBy(T candidate) => _left.IsSatisfiedBy(candidate) || _right.IsSatisfiedBy(candidate);
}
public class OrNotSpecification<T> : CompositeSpecification<T>
{
private ISpecification<T> _left;
private ISpecification<T> _right;
public OrNotSpecification(ISpecification<T> left, ISpecification<T> right)
{
_right = right;
}
public override bool IsSatisfiedBy(T candidate) => _left.IsSatisfiedBy(candidate) || !_right.IsSatisfiedBy(candidate);
}
public class NotSpecification<T> : CompositeSpecification<T>
{
public NotSpecification(ISpecification<T> other) => this.other = other;
public override bool IsSatisfiedBy(T candidate) => !other.IsSatisfiedBy(candidate);
}
</syntaxhighlight>
=== Python ===
{{Further|Python (programming language)}}
<syntaxhighlight lang="python">
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Any
class BaseSpecification(ABC):
def is_satisfied_by(self, candidate: Any) -> bool:
def __call__(self, candidate: Any) -> bool:
return self.is_satisfied_by(candidate)
def __and__(self, other: "BaseSpecification") -> "AndSpecification":
return AndSpecification(self, other)
def __or__(self, other: "BaseSpecification") -> "OrSpecification":
return OrSpecification(self, other)
def __neg__(self) -> "NotSpecification":
@dataclass(frozen=True)
class AndSpecification(BaseSpecification):
first: BaseSpecification
second: BaseSpecification
def is_satisfied_by(self, candidate: Any) -> bool:
return self.first.is_satisfied_by(candidate) and self.second.is_satisfied_by(candidate)
@dataclass(frozen=True)
class OrSpecification(BaseSpecification):
first: BaseSpecification
second: BaseSpecification
def is_satisfied_by(self, candidate: Any) -> bool:
return self.first.is_satisfied_by(candidate) or self.second.is_satisfied_by(candidate)
@dataclass(frozen=True)
class NotSpecification(BaseSpecification):
subject: BaseSpecification
def is_satisfied_by(self, candidate: Any) -> bool:
return not self.subject.is_satisfied_by(candidate)
</syntaxhighlight>
=== C++ ===
{{Further|C++}}
<syntaxhighlight lang="cpp">
template <class T>
class ISpecification
{
public:
virtual ~ISpecification() = default;
virtual bool IsSatisfiedBy(T Candidate) const = 0;
virtual ISpecification<T>* And(const ISpecification<T>& Other) const = 0;
virtual ISpecification<T>* AndNot(const ISpecification<T>& Other) const = 0;
virtual ISpecification<T>* Or(const ISpecification<T>& Other) const = 0;
virtual ISpecification<T>* OrNot(const ISpecification<T>& Other) const = 0;
virtual ISpecification<T>* Not() const = 0;
};
template <class T>
class CompositeSpecification : public ISpecification<T>
{
public:
virtual bool IsSatisfiedBy(T Candidate) const override = 0;
virtual ISpecification<T>* And(const ISpecification<T>& Other) const override;
virtual ISpecification<T>* AndNot(const ISpecification<T>& Other) const override;
virtual ISpecification<T>*
virtual ISpecification<T>* OrNot(const ISpecification<T>& Other) const override;
virtual ISpecification<T>* Not() const override;
};
template <class T>
class AndSpecification final : public CompositeSpecification<T>
{
public:
const ISpecification<T>& Left;
const ISpecification<T>& Right;
AndSpecification(const ISpecification<T>& InLeft, const ISpecification<T>& InRight)
: Left(InLeft),
Right(InRight) { }
virtual bool IsSatisfiedBy(T Candidate) const override
{
return Left.IsSatisfiedBy(Candidate) && Right.IsSatisfiedBy(Candidate);
}
};
template <class T>
ISpecification<T>* CompositeSpecification<T>::And(const ISpecification<T>& Other) const
{
return new AndSpecification<T>(*this, Other);
}
template <class T>
class AndNotSpecification final : public CompositeSpecification<T>
{
public:
const ISpecification<T>& Left;
const ISpecification<T>& Right;
AndNotSpecification(const ISpecification<T>& InLeft, const ISpecification<T>& InRight)
: Left(InLeft),
Right(InRight) { }
virtual bool IsSatisfiedBy(T Candidate) const override
{
return Left.IsSatisfiedBy(Candidate) && !Right.IsSatisfiedBy(Candidate);
}
};
template <class T>
class OrSpecification final : public CompositeSpecification<T>
{
public:
const ISpecification<T>& Left;
const ISpecification<T>& Right;
OrSpecification(const ISpecification<T>& InLeft, const ISpecification<T>& InRight)
: Left(InLeft),
Right(InRight) { }
virtual bool IsSatisfiedBy(T Candidate) const override
{
return Left.IsSatisfiedBy(Candidate) || Right.IsSatisfiedBy(Candidate);
}
};
template <class T>
class OrNotSpecification final : public CompositeSpecification<T>
{
public:
const ISpecification<T>& Left;
const ISpecification<T>& Right;
OrNotSpecification(const ISpecification<T>& InLeft, const ISpecification<T>& InRight)
: Left(InLeft),
Right(InRight) { }
virtual bool IsSatisfiedBy(T Candidate) const override
{
return Left.IsSatisfiedBy(Candidate) || !Right.IsSatisfiedBy(Candidate);
}
};
template <class T>
class NotSpecification final : public CompositeSpecification<T>
{
public:
const ISpecification<T>& Other;
NotSpecification(const ISpecification<T>& InOther)
: Other(InOther) { }
virtual bool IsSatisfiedBy(T Candidate) const override
{
return !Other.IsSatisfiedBy(Candidate);
}
};
template <class T>
ISpecification<T>* CompositeSpecification<T>::AndNot(const ISpecification<T>& Other) const
{
return new AndNotSpecification<T>(*this, Other);
}
template <class T>
ISpecification<T>* CompositeSpecification<T>::Or(const ISpecification<T>& Other) const
{
return new OrSpecification<T>(*this, Other);
}
template <class T>
ISpecification<T>* CompositeSpecification<T>::OrNot(const ISpecification<T>& Other) const
{
return new OrNotSpecification<T>(*this, Other);
}
template <class T>
ISpecification<T>* CompositeSpecification<T>::Not() const
{
return new NotSpecification<T>(*this);
}
</syntaxhighlight>
=== TypeScript ===
{{Further|TypeScript}}
<syntaxhighlight lang="typescript">
export interface ISpecification {
isSatisfiedBy(candidate: unknown): boolean;
and(other: ISpecification): ISpecification;
andNot(other: ISpecification): ISpecification;
or(other: ISpecification): ISpecification;
orNot(other: ISpecification): ISpecification;
not(): ISpecification;
}
export abstract class CompositeSpecification implements ISpecification {
abstract isSatisfiedBy(candidate: unknown): boolean;
and(other: ISpecification): ISpecification {
return new AndSpecification(this, other);
}
andNot(other: ISpecification): ISpecification {
return new AndNotSpecification(this, other);
}
or(other: ISpecification): ISpecification {
return new OrSpecification(this, other);
}
orNot(other: ISpecification): ISpecification {
return new OrNotSpecification(this, other);
}
not(): ISpecification {
return new NotSpecification(this);
}
}
export class AndSpecification extends CompositeSpecification {
constructor(private leftCondition: ISpecification, private rightCondition: ISpecification) {
super();
}
isSatisfiedBy(candidate: unknown): boolean {
return this.leftCondition.isSatisfiedBy(candidate) && this.rightCondition.isSatisfiedBy(candidate);
}
}
export class AndNotSpecification extends CompositeSpecification {
constructor(private leftCondition: ISpecification, private rightCondition: ISpecification) {
super();
}
isSatisfiedBy(candidate: unknown): boolean {
return this.leftCondition.isSatisfiedBy(candidate) && this.rightCondition.isSatisfiedBy(candidate) !== true;
}
}
export class OrSpecification extends CompositeSpecification {
constructor(private leftCondition: ISpecification, private rightCondition: ISpecification) {
super();
}
isSatisfiedBy(candidate: unknown): boolean {
return this.leftCondition.isSatisfiedBy(candidate) || this.rightCondition.isSatisfiedBy(candidate);
}
}
export class OrNotSpecification extends CompositeSpecification {
constructor(private leftCondition: ISpecification, private rightCondition: ISpecification) {
super();
}
isSatisfiedBy(candidate: unknown): boolean {
return this.leftCondition.isSatisfiedBy(candidate) || this.rightCondition.isSatisfiedBy(candidate) !== true;
}
}
export class NotSpecification extends CompositeSpecification {
constructor(private wrapped: ISpecification) {
super();
}
isSatisfiedBy(candidate: unknown): boolean {
return !this.wrapped.isSatisfiedBy(candidate);
}
}
</syntaxhighlight>
==Example of use==
In the next example, invoices are retrieved and sent to a collection agency if:
# they are overdue,
# notices have been sent, and
# 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 <code>OverdueSpecification</code> class that is satisfied when an invoice's due date is 30 days or older, a <code>NoticeSentSpecification</code> class that is satisfied when three notices have been sent to the customer, and an <code>InCollectionSpecification</code> 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 <code>SendToCollection</code> 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.
<syntaxhighlight lang="csharp">
var overdue = new OverdueSpecification();
var noticeSent = new NoticeSentSpecification();
var inCollection = new InCollectionSpecification();
// Example of specification pattern logic chaining
var sendToCollection = overdue.And(noticeSent).And(inCollection.Not());
var invoices = InvoiceService.GetInvoices();
foreach (var invoice in invoices)
{
if (sendToCollection.IsSatisfiedBy(invoice))
{
}
}
</syntaxhighlight>
==References==
Line 333 ⟶ 553:
* [https://github.com/Happyr/Doctrine-Specification Happyr Doctrine Specification in PHP] by Happyr
* [https://github.com/neoneye/SpecificationPattern The Specification Pattern in Swift] by Simon Strandgaard
* [https://github.com/thiagodp/spec-pattern The Specification Pattern in TypeScript and JavaScript] by Thiago Delgado Pinto
* [https://web.archive.org/web/20110724151447/http://www.dpdk.nl/opensource/specification-pattern-for-selection-on-lists specification pattern in flash actionscript 3] by Rolf Vreijdenberger
{{Design
[[Category:Architectural pattern (computer science)]]
[[Category:Software design patterns]]
[[Category:Programming language comparisons]]
<!-- Hidden categories below -->
[[Category:Articles with example C Sharp code]]
[[Category:Articles with example C++ code]]
[[Category:Articles with example JavaScript code]]
[[Category:Articles with example Python (programming language) code]]
|