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:
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.
Private _requiredFeature As ContainerFeature
The Constructor
is very simple, it requires the desired feature as a parameter and sets the member variable.
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.
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.
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:
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.
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
.
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.