VB.NET 简洁规范





5.00/5 (1投票)
这是一个关于规范设计模式的四部分系列文章的第二部分。
引言
继 第一部分 中对规范的介绍之后,本文展示了该设计模式的一个简单实现。
- 第 1 部分 - 规范设计模式简介
- 第 2 部分 - 在 VB.NET 中简单实现该模式
- 第三部分 - 带运算符重载的通用规范
- 第 4 部分 - 使用通用规范
背景
示例代码基于 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
类有三个属性 - Features
、Capacity
和 Drums
。
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 的规范,我们可以以与创建 IsArmored
或 IsVentilated
规范完全相同的方式进行。唯一的区别在于我们如何实现 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
我们只需使用 IsArmored
和 IsLeadLined
规范,并将每个规范的结果组合起来创建一个名为 IsArmoredOrLeadLined
的新规范。
结果并不是一个糟糕的解决方案。它易于阅读,易于理解,但还可以更好。我们不应该仅仅为了组合两个其他规范而创建一个全新的规范。
在本系列的下一部分中,我们将创建一个通用规范,它解决了此解决方案的两个主要问题。它将是通用的,因此可以用于任何类型的对象,而不仅仅是容器。它还将允许将规范组合成更复杂的规范,而无需创建新类。
一旦您开始使用规范,您就会发现它们提供了一个强大的词汇来捕捉现实世界的需求。规范设计模式允许您创建与现实世界领域中使用的语言非常匹配的代码。