65.9K
CodeProject 正在变化。 阅读更多。
Home

VB.NET 简洁规范

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2009年4月5日

CPOL

5分钟阅读

viewsIcon

25533

downloadIcon

147

这是一个关于规范设计模式的四部分系列文章的第二部分。

引言

第一部分 中对规范的介绍之后,本文展示了该设计模式的一个简单实现。

背景

示例代码基于 Eric Evans 的著作《领域驱动设计》中的化学品包装示例。

使用代码

附加的示例项目包含 HandRollesTests.vb 文件中的单元测试。这些测试说明了如何使用规范。该项目使用 NUnit 进行单元测试。如果您没有安装 NUnit,则应从项目中删除 HandRolledTests.vb 并删除对 nunit.framework 的引用。

构建一个简单的规范

在本系列文章的 第一部分 中,我们讨论了规范设计模式以及为什么您可能想要使用它。在本文中,我们将实现一个可用的示例。

我们将在这里构建的规范是为第一篇文章中讨论的 Container 对象创建的。在本系列的第三部分中,我们将探讨如何更进一步,创建一个可以用于任何对象的通用规范。

那么,让我们开始吧。您会从 第一部分 回忆起,我们正在处理化学品桶。我们的挑战是将这些桶装入容器中。每个桶都可能对能够容纳它的容器类型有要求。我们的 Drum 类代码如下:

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

Drum 类中几乎没有逻辑,只有一个字符串和一个整数用于化学品的名称和数量,以及一个 ContainerSpecification 对象,该对象定义了化学品所需的容器类型。正是这个 ContainerSpecification 对象是我们本文关注的重点。

在此之前,我们还需要一个 Container 类。Container 是将被规范检查的对象。

一个容器可以容纳多个化学品桶,而规范将提供一种方法来检查每个桶是否都在正确的容器类型中。

在本示例中,我使用了一个标志枚举,列出了容器的属性,这些属性是桶可能要求的。请注意,枚举值以 2 的幂递增(每个值是前一个值的两倍)。这允许组合值。例如,Armored 是 1,Airtight 是 4,Armored AND Airtight 是 5。

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

Container 类有三个属性 - FeaturesCapacityDrums

Features 使用上面描述的 ContainerFeature 枚举。Capacity 是容器的总容量,桶的总容量不能超过此值。Drums 属性只是已添加到容器中的桶的列表。

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

该类包含添加桶的方法 (AddDrum)、检查容器剩余空间的方法 (RemainingSpace)、检查桶是否适合容器的方法 (HasSpaceFor)、检查容器是否适合特定桶的方法 (CanAccommodate),以及一个查看容器中所有桶以确保容器安全包装的函数 (IsSafelyPacked)。从上面的列表可以看出,这些函数中的每一个都相当简单。

所以,我们有了桶,也有了可以装桶的容器。是时候创建一个将这两个对象连接起来的规范了。好消息是,这是一项非常简单的任务。

我们知道 Drum 类有一个 ContainerSpecification 类型的属性,因此我们需要创建该类。我们也知道这个类唯一提供的行为是名为 IsSatisfiedBy 的方法,该方法接受一个容器并返回一个布尔值。因此,我们已经知道 ContainerSpecification 对象应该是什么样子。

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

请注意,我们使用 MustInherit 声明该类,并要求重写 IsSatisfiedBy 函数。ContainerSpecification 是一个抽象基类。我们需要实现继承自 ContainerSpecification 的具体类,它们负责检查实际所需的属性。这是一个具体的类示例。

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

就这样。我们定义了一个抽象的 ContainerSpecification 类,并从中继承来定义一个可以检查容器是否为 Armored 的规范。我们可以在代码中这样使用它。

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

我们将 IsArmored 规范的一个新实例传递给我们的 drum 的构造函数,并通过这样做,我们确保 TNT 只能存储在 Armored 容器中。现在,容器可以自己检查以确保其包装安全。

如果我们想检查一个容器是否是通风的,我们只需创建一个继承自 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

使用标志枚举来表示容器的属性是本示例特有的,对规范的整体思想并不重要。IsSatisfiedBy 方法可以同样轻松地查看任何其他属性或方法。

如果您看到了这里,您可能会想知道如何组合规范。如果我们需要的容器既是 Armored 又是 LeadLined,或者化学品可以放在 Armored 或 LeadLined 的容器中,但不能是 Ventilated 的,该怎么办?

在本系列的第三部分中,我们将探讨一种优雅的方式来将单个规范组合成这些复合语句。现在,我们将看一种不太优雅的解决方案。

如果我们想创建一个测试容器是 Armored 或 LeadLined 的规范,我们可以以与创建 IsArmoredIsVentilated 规范完全相同的方式进行。唯一的区别在于我们如何实现 IsSatisfiedBy 方法。

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

我们只需使用 IsArmoredIsLeadLined 规范,并将每个规范的结果组合起来创建一个名为 IsArmoredOrLeadLined 的新规范。

结果并不是一个糟糕的解决方案。它易于阅读,易于理解,但还可以更好。我们不应该仅仅为了组合两个其他规范而创建一个全新的规范。

在本系列的下一部分中,我们将创建一个通用规范,它解决了此解决方案的两个主要问题。它将是通用的,因此可以用于任何类型的对象,而不仅仅是容器。它还将允许将规范组合成更复杂的规范,而无需创建新类。

一旦您开始使用规范,您就会发现它们提供了一个强大的词汇来捕捉现实世界的需求。规范设计模式允许您创建与现实世界领域中使用的语言非常匹配的代码。

© . All rights reserved.