Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

Introduction to the Specification Design Pattern

3.57/5 (3 votes)
5 Apr 2009CPOL5 min read 30K  
Part one of a four part series of articles on the Specification Design Pattern.

Introduction

This is the first in a four part series of articles on the Specification Design Pattern.

Background

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.

VB
Dim isOverdue as New OverdueInvoiceSpec
    If isOverdue.IsSatisfiedBy(invoice) then
    ' invoice object Is Overdue
End If

The 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:

VB
Public Function IsSatisfiedBy(ByVal candidate As Invoice) As Boolean
    Return candidate.IsOverdue
End Function

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.

VB
Public Class Drum
  Private _chemical As String
  Private _size As Int32

  Public ReadOnly Property Chemical() As String
    Get
      Return _chemical
    End Get
  End Property

  Public ReadOnly Property Size() As Int32
    Get
      Return _size
    End Get
  End Property

  Public Sub New(ByVal chemical As String, ByVal size As Int32)
    _chemical = chemical
    _size = size
  End Sub
End Class

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.

The IsSuitableContainer method could end up looking something like this:

VB
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
    ...
    ...
End Function

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.

VB
Private _requiredContainer As ContainerSpecification

Public ReadOnly Property RequiredContainer() As ContainerSpecification
  Get
    Return _requiredContainer
  End Get
End Property

And, let's modify the constructor so that the specification can be set when the chemical drum is being created.

VB
Public Sub New(ByVal chemical As String, ByVal size As Int32, _
                     ByVal requiredContainer As  ContainerSpecification)
  _chemical = chemical
  _size = size
  _requiredContainer = requiredContainer
End Sub

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.

VB
Public Class IsArmored
       Inherits ContainerSpecification

  Public Overrides Function IsSatisfiedBy(ByVal candidate As Container) As Boolean
    Return candidate.IsArmored()
  End Function
End Class

Let's create a drum to see the specification in action.

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

VB
If tntDrum.RequiredContainer.IsSatisfiedBy(container) then
  ' tntDrum can be safely packaged in container
End If

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.

VB
Public Function isSafelyPacked() As Boolean
  Dim blnIsSafe As Boolean = True
  For Each drum As Drum In _drums
    blnIsSafe = blnIsSafe And drum.RequiredContainer.IsSatisfiedBy(Me)
  Next
  Return blnIsSafe
End Function

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. IsWatertight, IsAirtight, IsVacuumSealed, 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)