Click here to Skip to main content
Click here to Skip to main content
Go to top

A Simple Specification in VB.NET

, 5 Apr 2009
Rate this:
Please Sign up or sign in to vote.
Part two of a four part series of articles on the Specification Design Pattern.

Introduction

Following on from the introduction to Specifications in part one, this article shows a simple implementation of the design pattern.

Background

The sample code is based on the Chemical Packaging example in Eric Evans' book, 'Domain Driven Design'.

Using the code

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

Building a simple Specification

In part one of this series of articles, we looked at the Specification Design Pattern and why you might want to use it. In this article, we'll implement a working example.

The specification we build here will be created for the Container object that we discussed in the first article. In part 3 of this series, we'll look at how to go a step further and create a generic specification that will work with any object.

So, let's get started. You'll recall from part one that we're dealing with drums of chemicals. Our challenge is to load these drums into containers. Each drum can have requirements for the type of container that can hold it. Our drum class is coded as follows:

Public Class Drum
  Private _chemical As String
  Private _size As Int32
  Private _requiredContainer As ContainerSpecification


  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 ReadOnly Property RequiredContainer() As ContainerSpecification
    Get
      Return _requiredContainer
    End Get
  End Property

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

There's virtually no logic in the drum class, just a string and integer for the name and quantity of the chemical, and a ContainerSpecification object that defines the type of container that the chemical requires. It is this ContainerSpecification object that we're concerned with in this article.

Before we get to that, we also need a Container class. The Container is the thing that will be examined by the specification.

A container can hold multiple chemical drums, and the specification will provide a way of checking that each drum is in the right kind of container.

For the purposes of this example, I'm using a flags enum which lists the attributes of a container that a drum might require. Note that the enum values increase in powers of 2 (each is double the previous entry). This allows values to be combined. E.g. Armored is 1, Airtight is 4, Armored AND Airtight is 5.

<Flags()> _
Public Enum ContainerFeature
  None = 0
  Armored = 1 
  Ventilated = 2
  Airtight = 4
  LeadLined = 8
End Enum

The Container class has three properties - Features, Capacity, and Drums.

Features uses the ContainerFeature enum described above. Capacity is the overall capacity of the container, the combined capacity of the drums can't exceed this value. The Drums property is simply a list of the drums that have been added to the container.

Public Class Container
  Private _features As ContainerFeature
  Private _capacity As Int32
  Private _drums As List(Of Drum)

  Public ReadOnly Property Features() As ContainerFeature
    Get
      Return _features
    End Get
  End Property

  Public ReadOnly Property capacity() As Int32
    Get 
      Return _capacity
    End Get
  End Property

  Public ReadOnly Property Drums() As List(Of Drum)
    Get
      Return _drums
    End Get
  End Property

  Public Sub AddDrum(ByVal drum As Drum)
    _drums.Add(drum)
  End Sub

  Public Function RemainingSpace() As Int32
    Dim usedSpace As Int32 = 0
    For Each drum As Drum In _drums
      usedSpace += drum.Size
    Next
    Return _capacity - usedSpace
  End Function

  Public Function HasSpaceFor(ByVal drum As Drum) As Boolean
    Return RemainingSpace() >= drum.Size
  End Function

  Public Function CanAccommodate(ByVal drum As Drum) As Boolean
    Return hasSpaceFor(drum) And drum.RequiredContainer.IsSatisfiedBy(Me)
  End Function

  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

  Public Sub New(ByVal capacity As Int32, _
             ByVal features As ContainerFeature)
    _capacity = capacity
    _features = features
    _drums = New List(Of Drum)
  End Sub
End Class

The class contains methods for adding a drum (AddDrum), checking the remaining space in the container (RemainingSpace), checking if a drum can fit in the container (HasSpaceFor), checking if the container is suitable for a particular drum (CanAccommodate), and a function that looks at all drums in a container to ensure that the container is safely packaged (IsSafelyPacked). Each of these functions is fairly simple, as can be seen from the listing above.

So, we have drums, and we have containers to put the drums in. It's time to create a specification that links these two objects together. The good news is that this is a very simple task.

We know that the Drum class has a property of type ContainerSpecification, so we need to create that class. We also know that the only behaviour this class provides is a method called IsSatisfiedBy which accepts a container and returns a boolean. So, we already know what the ContainerSpecification object should look like:

Public MustInherit Class ContainerSpecification
  Public MustOverride Function IsSatisfiedBy(ByVal candidate As Container) As Boolean
End Class

Note that we declare the class using MustInherit, and we require the IsSatisfiedBy function to be overridden. ContainerSpecification is an abstract base class. We need to implement concrete classes that inherit from ContainerSpecification and which check for the actual desired attributes. Here's an example of such a concrete class:

Public Class IsArmored
       Inherits ContainerSpecification

  Public Overrides Function IsSatisfiedBy(ByVal candidate As Container) As Boolean
    Return CType(candidate.Features And ContainerFeature.Armored, Boolean)
  End Function
End Class

And, that's all there is to it. We've defined an abstract ContainerSpecification class, and inherited from it to define a specification that can check if a container is Armored. We can use this in our code as follows:

Dim tntDrum As New Drum("TNT", 3000, New IsArmored)

We pass a new instance of the IsArmored specification to the constructor of our drum, and in doing so, we ensure that TNT can only be stored in an Armored container. Now, a container can check itself to make sure it’s safely packed.

If we need to check that a container is ventilated, we just create another specification that inherits from ContainerSpecification.

Public Class IsVentilated
       Inherits ContainerSpecification

  Public Overrides Function IsSatisfiedBy(ByVal candidate As Container) As Boolean
    Return CType(candidate.Features And ContainerFeature.Ventilated, Boolean)
  End Function
End Class

The use of a flags enum to represent features of a container is something specific to this example, it is not important to the overall idea of specifications. The IsSatisfiedBy method could just as easily be looking at any other property or method.

If you've come this far, you may find yourself wondering about how specifications can be combined. What if we needed a container that is both Armored AND LeadLined, or what if a chemical could be in a container that was either Armored OR LeadLined, but not Ventilated?

In part 3 of this series, we'll look at an elegant way of combining individual specifications into these kinds of composite statements. For now, we'll look at a less elegant solution.

If we want to create a specification that tests if a container is either Armored OR LeadLined, we can do so in exactly the same way we created the IsArmored or IsVentilated specifications. The only difference lies in the way we implement the IsSatisfiedBy method.

Public Class IsArmoredOrLeadLined
       Inherits ContainerSpecification
  Public Overrides Function IsSatisfiedBy(ByVal candidate As Container) As Boolean
    Dim IsArmoredSpec As New IsArmored
    Dim IsLeadLinedSpec As New IsLeadLined
    Return IsArmoredSpec.IsSatisfiedBy(candidate) Or _
           IsLeadLinedSpec.IsSatisfiedBy(candidate)
  End Function
End Class

We simply use the IsArmored and IsLeadLined specs, and we combine the result from each to create a new spec called IsArmoredOrLeadLined.

The result is isn't a bad solution. It's readable, easy to understand, but it could be better. We shouldn't need to create a whole new specification just to combine two others.

In the next part of this series, we'll create a generic specification that solves the two major problems with this solution. It will be generic so it can be used with any kind of object, not just containers. It will also allow specifications to be combined into more complex specifications without the need to create a new class.

Once you start working with specifications, you will find that they provide a powerful vocabulary for capturing real world requirements. The Specification Design Pattern allows you to create code that very closely matches the language used in the real world domain.

License

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

Share

About the Author

No Biography provided

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 5 Apr 2009
Article Copyright 2009 by Richard A. Dalton
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid