Specification pattern
A Specification pattern outlines a unit of business logic that is combinable with other business logic units. In this pattern, a unit of Business Logic inherits it's 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 as a persistence repository.
Diagram
+-----------------+ | <<Interface>> | | ISpecification | |-----------------| |-----------------| +-----------------------+ |And() | |Composite Specification| |IsSatisifiedBy() |<>------| | |Not() | |-----------------------| |Or() | |-----------------------| +-----------------+ |And() | |IsSatisifiedBy() | +-------------|>|Not() |<|------+ | |Or() | | | +-----------------------+ | | ' | | /+\ | | | | | | | | | | | | | +-----------+---------+ +---------------+-----+ +---------------------+ | AndSpecification | | OrSpecification | | NotSpecification | |---------------------| |---------------------| |---------------------| |ISpecification: One | |ISpecification: One | |ISpecification: | |ISpecification: Other| |ISpecification: Other| | Wrapped | |---------------------| |---------------------| |---------------------| |AndSpecification() | |OrSpecification() | |NotSpecification() | |IsSatisfiedBy() | |IsSatisfiedBy() | |IsSatisfiedBy() | +---------------------+ +---------------------+ +---------------------+
Code Examples
public interface ISpecification { bool IsSatisfiedBy(object candidate); ISpecification And(ISpecification other); ISpecification Or(ISpecification other); ISpecification Not(ISpecification other); } public class CompositeSpecification : ISpecification { public virtual bool IsSatisfiedBy(object candidate) { throw new Exception("The method or operation is not implemented."); } public ISpecification And(ISpecification other) { return new AndSpecification(this, other); } public ISpecification Or(ISpecification other) { return new OrSpecification(this, other); } public ISpecification Not(ISpecification other) { return new NotSpecification(this); } } public class AndSpecification : CompositeSpecification { private ISpecification One; private ISpecification Other; public AndSpecification(ISpecification x, ISpecification y) { One = x; Other = y; } public override bool IsSatisfiedBy(object candidate) { return One.IsSatisfiedBy(candidate) && Other.IsSatisfiedBy(candidate); } } public class OrSpecification : CompositeSpecification { private ISpecification One; private ISpecification Other; public OrSpecification(ISpecification x, ISpecification y) { One = x; Other = y; } public override bool IsSatisfiedBy(object candidate) { return One.IsSatisfiedBy(candidate) || Other.IsSatisfiedBy(candidate); } } public class NotSpecification : CompositeSpecification { private ISpecification Wrapped; public NotSpecification(ISpecification x) { Wrapped = x; } public override bool IsSatisfiedBy(object candidate) { return !Wrapped.IsSatisfiedBy(candidate); } }
Example of use
In this example, we are retrieving invoices and sending them to a collection agency if they are overdue, notices have been sent and they are not already with the collection agency.
We previously defined an OverdueSpecification class that it is satisified when an invoice's due date is 30 days or older, a NoticeSentSpecification class that is satisified when three notices have sent to the customer and a InCollectionSpecification class that is satisfied when an invoice has already been sent to the collection agency.
Using these three specifications, we created a new specification called SendToCollection which will be satisified when an invoice is overdue, notices have been sent to the customer and is not already with the collection agency.
OverDueSpecification OverDue = new OverDueSpecification(); NoticeSentSpecification NoticeSent = new NoticeSentSpecification(); InCollectionSpecification InCollection = new InCollectionSpecification(); ISpecification SendToCollection = OverDue.And(NoticeSent.Not(InCollection)); InvoiceCollection = Service.GetInvoices(); foreach(Invoice currentInvoice in InvoiceCollection) { if(SendToCollection.IsSatisfiedBy(currentInvoice)) { currentInvoice.SendToCollection(); } }
References
Evans, E: "Domain-Driven Design.", page 224. Addison-Wesley, 2004.
External Links
- Specifications by Eric Evans and Martin Fowler
- The Specification Pattern: A Primer by Matt Berther