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

规格设计模式简介

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.57/5 (3投票s)

2009年4月5日

CPOL

5分钟阅读

viewsIcon

30482

这是关于 Specification 设计模式的四部分系列文章的第一部分。

引言

这是关于 Specification 设计模式的四部分系列文章的第一篇。

背景

Specification 设计模式由 Eric Evans 创建。他在其著作《领域驱动设计》中对此模式进行了描述。本系列文章将演示如何使用 VB.NET 实现该模式。

Specification 设计模式

Specification 设计模式使用一个对象来表示关于另一个对象的声明。一旦实现了某个规范,就可以用它来检查候选对象是否符合该规范。

一个非常简单的例子是关于发票是否逾期的规范。

Dim isOverdue as New OverdueInvoiceSpec
    If isOverdue.IsSatisfiedBy(invoice) then
    ' invoice object Is Overdue
End If

IsSatisfiedBy 方法包含确定发票是否逾期的逻辑。这可能很简单,只需检查发票日期,也可能更复杂。该方法可能需要检查发票适用客户的付款历史记录。

规范的任务可能更简单。在某些情况下,候选对象可能实际上有一个方法或属性可以直接回答规范所提出的问题。例如,发票可能提供一个 IsOverdue 方法。在这种情况下,IsSatisfiedBy 方法可以这样编写:

Public Function IsSatisfiedBy(ByVal candidate As Invoice) As Boolean
    Return candidate.IsOverdue
End Function

现在,您可能会想,如果目标对象已经有一个回答问题的现有方法,为什么还要费力去创建一个规范呢?原因在于,将对象的期望状态与对象本身分开封装可能很有用。这允许我们以抽象的方式思考对象的状态,而不必关注具体的实例。

我们可以将期望状态作为参数传递给方法,或者将其作为类的属性附加。然后,该方法或类可以使用该规范来检查它想要的任何候选对象。

Eric Evans 出色的著作《领域驱动设计》中提供了一个很好的例子。Evans 使用了一个化学品仓库系统的例子,该系统将化学品装入不同类型的容器中。容器可以是装甲的、通风的、两者的,或者都不是。每种化学品都有特定类型的容器要求。

容器对象可能具有简单的属性或方法来指示其是否为装甲或通风,但我们如何定义化学品所需的容器类型呢?

让我们看一下我们将用于表示化学品桶的类。桶将使用字符串存储化学品的名称;对于数量,我们将使用整数。

Public Class Drum
  Private _chemical As String
  Private _size As Int32

  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 Sub New(ByVal chemical As String, ByVal size As Int32)
    _chemical = chemical
    _size = size
  End Sub
End Class

我们可以尝试在此类中实现一个名为 IsSuitableContainer 的方法,该方法接受一个容器。然后,该方法可以查看桶中化学品的类型,并决定提供的容器是否合适。

这种方法的问题在于,我们的化学品桶类将需要了解我们将来使用的每种化学品,并且需要知道每种化学品的包装要求。

IsSuitableContainer 方法最终可能看起来像这样:

Public Function IsSuitableContainer(ByVal candidate As Container) As Boolean
    If _chemical = "TNT" then return candidate.IsArmored
    If _chemical = "Uranium" then return candidate.IsArmored
    If _ chemical = "Sulpher" then return candidate.IsVentilated
    ...
    ...
End Function

这不是一个好的解决方案。更好的选择是在 Drum 类中添加一个属性来指定它所需的容器类型。换句话说,我们需要将容器规范作为属性附加到我们的化学品桶中。

这意味着创建一个类来表示容器规范。我们将在后面的部分介绍如何实现规范,但现在,让我们在我们的化学品 Drum 类中添加一个属性来保存该规范。

Private _requiredContainer As ContainerSpecification

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

创建规范只需继承自 ContainerSpecification 即可。我们将在本系列的第二部分更详细地介绍这一点。目前,知道我们正在创建一个名为 IsArmored 的类,该类可以检查容器是否为装甲,并且由于它继承自 ContainerSpecification,因此可以传递给化学品桶的构造函数就足够了。

Public Class IsArmored
       Inherits ContainerSpecification

  Public Overrides Function IsSatisfiedBy(ByVal candidate As Container) As Boolean
    Return candidate.IsArmored()
  End Function
End Class

让我们创建一个桶来实际演示规范。

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

现在,我们可以将任何容器对象与桶所需的容器进行比较,以查看它是否适合该化学品。

If tntDrum.RequiredContainer.IsSatisfiedBy(container) then
  ' tntDrum can be safely packaged in container
End If

我们可以在容器上实现一个 IsSafelyPacked 方法,该方法遍历容器中的所有化学品桶,并检查容器是否适合每个桶。

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

如果容器未能通过任何桶的 requiredContainer 规范,则该容器未安全包装。

可能还有其他我们尚未考虑到的属性。例如 IsWatertightIsAirtightIsVacuumSealedIsLeadLined 等。只要新规范继承自 ContainerSpecification,我们就可以将其传递给化学品桶的构造函数,并指明该桶只能放置在那种类型的容器中。

在本系列的第二部分,我们将实现 ContainerSpecification 并展示如何继承它来创建具体的容器规范。在第三部分,我们将更进一步,创建一个泛型规范,该规范可以作为任何类型对象的规范的基础。

© . All rights reserved.