This is the first in a four part series of articles on the Specification Design Pattern.
The Specification Design Pattern was created by Eric Evans. He provides a description of the pattern in his book 'Domain Driven Design'. This series of articles shows how to implement the pattern using VB.NET.
The Specification Design Pattern
The Specification Design Pattern uses an object to represent a statement about another object. Once a specification has been implemented, it can be used to check whether a candidate object conforms to the specification or not.
A very simple example would be a specification of what it means for an invoice to be overdue.
Dim isOverdue as New OverdueInvoiceSpec
If isOverdue.IsSatisfiedBy(invoice) then
IsSatisfiedBy method contains the logic for figuring out if the invoice is overdue. This might be as simple as checking the date of the invoice, or it may be more complex. The method may have to examine the payment history of the customer that the invoice applies to.
The job of the specification may be even simpler. In some cases, the candidate object may actually have a method or property that directly answers the very question that the specification is asking. For example, the invoice might provide an
IsOverdue method. In that case, the
IsSatisfiedBy method can we written as follows:
Public Function IsSatisfiedBy(ByVal candidate As Invoice) As Boolean
Now, you might wonder why we'd bother with a specification if the target object already has a method that answers the question. The reason is that it can be useful to encapsulate a desired state of an object separate from the object itself. This allows us to think about the state of an object in an abstract way, not concerned with a specific instance.
We can pass the desired state as a parameter to a method, or attach it as a property of a class. The method or class can then use the specification to check any candidate objects that it wants.
A good example of this can be found in Eric Evans' excellent book, “Domain Driven Design”. Evans uses an example of a chemical warehouse system that packs chemicals into different types of containers. Containers can be Armored, Ventilated, both or neither. Each chemical has specific requirements for the type of container that can be used to hold it.
The container object can have simple methods or properties that indicate whether it is Armored or Ventilated, but how do we define the type of container that's required by a chemical?
Let's look at the class that we'll use to represent a drum of chemicals. A drum will use a string to store the name of the chemical; and for the quantity, we'll use an integer.
Public Class Drum
Private _chemical As String
Private _size As Int32
Public ReadOnly Property Chemical() As String
Public ReadOnly Property Size() As Int32
Public Sub New(ByVal chemical As String, ByVal size As Int32)
_chemical = chemical
_size = size
We could try to implement a method in this class called
IsSuitableContainer which accepts a container. The method could then look at the type of chemical in the drum and decide if the provided container is suitable.
The problem with this approach is that our chemical drum class would need to be aware of every kind of chemical that we work with in the future, and it would need to know the packaging requirements for each.
IsSuitableContainer method could end up looking something like this:
Public Function IsSuitableContainer(ByVal candidate As Container) As Boolean
If _chemical = "TNT" then return candidate.IsArmored
If _chemical = "Uranium" then return candidate.IsArmored
If _ chemical = "Sulpher" then return candidate.IsVentilated
That's not a good solution. A better option would be to add a property to the
Drum class that specifies the type of container that it requires. In other words, we need to attach a container specification to our chemical drum as a property.
This means creating a class to represent a container specification. We'll look at how to implement the specification later, but for now, let's add a property to our chemical
Drum class which will hold the specification.
Private _requiredContainer As ContainerSpecification
Public ReadOnly Property RequiredContainer() As ContainerSpecification
And, let's modify the constructor so that the specification can be set when the chemical drum is being created.
Public Sub New(ByVal chemical As String, ByVal size As Int32, _
ByVal requiredContainer As ContainerSpecification)
_chemical = chemical
_size = size
_requiredContainer = requiredContainer
Creating a specification is just a matter of inheriting from
ContainerSpecification. We'll look at this in more detail in part 2 of this series. For now, it's enough to know that we're creating a class called
IsArmored which can check if a container is Armored, and because it inherits from
ContainerSpecification, it can be passed to the constructor of a chemical drum.
Public Class IsArmored
Public Overrides Function IsSatisfiedBy(ByVal candidate As Container) As Boolean
Let's create a drum to see the specification in action.
Dim tntDrum As New Drum("TNT", 3000, New IsArmored)
We can now check any container object against the drum's required container to see if it is suitable for the chemical.
If tntDrum.RequiredContainer.IsSatisfiedBy(container) then
We can implement an
IsSafelyPacked method on a container that cycles through all the chemical drums in the container, checking that the container is suitable for each drum.
Public Function isSafelyPacked() As Boolean
Dim blnIsSafe As Boolean = True
For Each drum As Drum In _drums
blnIsSafe = blnIsSafe And drum.RequiredContainer.IsSatisfiedBy(Me)
If the container fails to pass the
requiredContainer specification of any drum, then the container is not safely packed.
There may be other attributes that we haven’t even thought of yet.
IsLeadLined etc. As long as the new specifications inherit from
ContainerSpecification, we can pass them to the constructor of our chemical drum and indicate that the drum can only be placed in a container of that type.
In part 2 of this series, we'll implement
ContainerSpecification and show how to inherit from it to create concrete container specifications. In part 3, we'll go a step further and create a generic specification that can be used as the basis for specifications for any kind of object.