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

Using the Specification Design Pattern

3.93/5 (4 votes)
5 Apr 2009CPOL3 min read 26.1K   179  
The final part of a four part series of articles on the Specification Design Pattern.

Introduction

This is the third in a series of four articles discussing the Specification Design Pattern and how to implement it using VB.NET.

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.

Using the code

The attached sample project includes Unit Tests in the GenericTests.vb file. These tests illustrate how to use the Generic Specification. The project uses NUnit for Unit Testing. If you do not have NUnit installed, you should remove GenericTests.vb from the project and remove the reference to nunit.framework.

Inheriting from the Generic Specification

In the third article in this series, we created a generic base Specification class which could be reused and inherited from to create specifications for any kind of object.

In this final article, we'll create a ContainerSpecification which uses the Specification class to work with the Container object that we coded in the earlier articles. Our ContainerSpecification is declared as follows:

VB
Public Class ContainerSpecification
Inherits Specification(Of Container)

You'll recall that when we declared the Specification class in Part III, we used the (Of T) notation to indicate that a type would be passed in when instances are created. By specifying (Of Container), we indicate to the Specification class that T means Container.

In Part II, we saw that the features of a container can be defined using a flags enum. Since a ContainerSpecification represents a required feature, we need a variable within the specification to store it.

VB
Private _requiredFeature As ContainerFeature

The Constructor is very simple, it requires the desired feature as a parameter and sets the member variable.

VB
Public Sub New(ByVal requiredFeature As ContainerFeature)
  _requiredFeature = requiredFeature
End Sub

We need to override the SpecificationRule function. This function is defined in the base Specification class, but each specification must implement its own logic. In this case, the logic is simple to check if a passed in Container (the candidate object) has the required feature as defined by the specification.

VB
Protected Overrides Function SpecificationRule(ByVal candidate As Container) As Boolean
  Return CType(candidate.Features And _requiredFeature, Boolean)
End Function

Finally, to make it possible to combine ContainerSpecifications using logical operators like AND, OR, and NOT, we need to overload those operators. These overloaded functions make use of the An<code>dOperator, OrOperator, and NotOperator functions in the base Specification class.

VB
Public Overloads Shared Operator And(ByVal a As ContainerSpecification, _
                 ByVal b As ContainerSpecification) As ContainerSpecification
  Return CType(Specification(Of Container).AndOperator(a, b), ContainerSpecification)
End Operator
 
Public Overloads Shared Operator Or(ByVal a As ContainerSpecification, _
                 ByVal b As ContainerSpecification) As ContainerSpecification
  Return CType(Specification(Of Container).OrOperator(a, b), ContainerSpecification)
End Operator
 
Public Overloads Shared Operator Not(ByVal a As ContainerSpecification) _
                 As ContainerSpecification
  Return CType(Specification(Of Container).NotOperator(a), ContainerSpecification)
End Operator

That's all there is to creating a specification. Most of the heavy lifting is now done in the base specification class. We no longer have to worry about how the AND, OR, and NOT operators are actually implemented, or how complex trees of specifications are represented.

The attached sample projects include Unit Tests that demonstrate how to use the specifications that we produce. The following are a few examples:

VB
Dim drum As New Drum("Acid", 500, New ContainerSpecification(ContainerFeature.Armored))
Dim container As New Container(10000, ContainerFeature.Armored)
Assert.IsTrue(drum.RequiredContainer.IsSatisfiedBy(container))

We declare a drum which holds a quantity of 500 of Acid, and requires an Armored container. We then declare a Container that is Armored.

The drum has a RequiredContainer property which is a Specification; its IsSatisfiedBy method checks our container and indicates that it is OK for the drum.

The next example shows how we can define a composite specification by combining existing specifications. We start with an Armored Specification and a Ventilated Specification. The AND and OR operators allow us to define new specifications that represent either Armored OR Ventilated, or both Armored AND Ventilated.

VB
' Create Specifications
Dim armoredSpec As New ContainerSpecification(ContainerFeature.Armored)
Dim ventilatedSpec As New ContainerSpecification(ContainerFeature.Ventilated)
Dim either As ContainerSpecification = armoredSpec Or ventilatedSpec
Dim both As ContainerSpecification = armoredSpec And ventilatedSpec

In the past, either and both would have been new separate classes. Here, they are just variables that combine the behaviours of existing classes. We can use them just like any Specification.

For example, we can pass a composite specification to the constructor of a Drum.

VB
' Create Drums, using Specifications to define acceptable containers
Dim uraniumDrum As New Drum("Uranium", 5000, either)
Dim tntDrum As New Drum("TNT", 5000, both)

Over the course of this four part series, we have learned about when and why to use Specifications. We have implemented a simple Specification, and we have created a generic base specification class that can be reused.

The sample projects that accompany this series should provide you with additional insight into how this useful Design Pattern works.

License

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