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.
A Generic Specification
The IsSatisfiedBy
function only needs to be written once in the base Specification class. The much simpler SpecificationRule
which is coded in our concrete specifications only has to concern itself with the very specific task of examining the candidate object that is passed to it.
The logic for combining specifications using Operators is implemented in three functions in the base Specification. The concrete specifications will call these methods from overloaded operator functions. This will be seen in part four of this series.
Protected Shared Function AndOperator(ByVal a As Specification(Of T), _
ByVal b As Specification(Of T)) As Specification(Of T)
Dim newSpec As Specification(Of T) = a.Copy
newSpec.SetAnd(b.Copy)
Return newSpec
End Function
Protected Shared Function OrOperator(ByVal a As Specification(Of T), _
ByVal b As Specification(Of T)) As Specification(Of T)
Dim newSpec As Specification(Of T) = a.Copy
newSpec.SetOr(b.Copy)
Return newSpec
End Function
Protected Shared Function NotOperator(ByVal a As Specification(Of T)) _
As Specification(Of T)
Dim newSpec As Specification(Of T) = a.Copy
newSpec.SetNot(Not a.IsNegated)
Return newSpec
End Function
These operator functions rely on four private functions: SetAnd
, SetOr
, SetNot
, and Copy
. These functions implement the real logic for creating complex combinations of specifications.
In simple terms, each specification has two branches from itself, each capable of pointing to another specification. When the specification is evaluated in the IsSatisfiedBy
function seen above, that function takes care of evaluating any specifications it points to. This continues recursively until all specifications are evaluated.
The SetAnd
and SetOr
methods are passed a specification that is to be joined to an existing specification. The SetAnd
method attaches it to the AND branch, and the SetOr
method attaches it to the OR branch. Obviously, the specification being added could itself be the root of a tree containing multiple specifications.
Private Sub SetAnd(ByVal spec As Specification(Of T))
If spec Is Nothing Then
AndSpec = spec
Exit Sub
End If
If AndSpec Is Nothing Then
AndSpec = spec
Else
AndSpec.SetAnd(spec)
End If
End Sub
Private Sub SetOr(ByVal spec As Specification(Of T))
If spec Is Nothing Then
OrSpec = spec
Exit Sub
End If
If OrSpec Is Nothing Then
OrSpec = spec
Else
OrSpec.SetOr(spec)
End If
End Sub
The NOT operator is a simple boolean flag. If it is set, then the result of the specification is negated.
Private Sub SetNot(ByVal value As Boolean)
IsNegated = value
End Sub
We also need a copy function that can copy a specification or a tree of specifications. This is used by the AndOperator
, OrOperator
, and NotOperator
functions.
Private Function Copy() As Specification(Of T)
Dim newSpec As Specification(Of T)
newSpec = CType(Me.MemberwiseClone, Specification(Of T))
newSpec.SetAnd(Nothing)
newSpec.SetOr(Nothing)
If Not AndSpec Is Nothing Then
newSpec.SetAnd(AndSpec.Copy)
End If
If Not OrSpec Is Nothing Then
newSpec.SetOr(OrSpec.Copy)
End If
Return newSpec
End Function
In the concluding part of this series, we'll build a new version of the ContainerSpecification
class which inherits from the Specification class. We'll then declare a number of ContainerSpecification
s and combine them.