领域驱动设计实践——流水号生成器(下)

0. 上篇回顾

上篇中我们使用测试驱动开发方法(Test-Driven Development)实现了一个简单的流水号生成器,并获得了一个初步的软件模型:

领域驱动设计实践——流水号生成器(下)_第1张图片

图1 编号生成器模型(V1)

熟悉设计模式的朋友们一眼就会看出来,这里运用了组合模式(Composite Pattern),把每个子流水号当做一个流水号来处理。虽然这个模型还能工作,但是我们仔细分析一下就会有很多疑问:

  1. ISerialNumberGenerator接口有什么用?为什么不直接使用抽象类(TSerialNumberGeneratorBase)?
  2. 客户需要验证流水号吗?即使需要,Validate函数应该返回Boolean吗?
  3. 调用NextSerialNumber函数时需要传入一个流水号,是不是意味着其调用者需要知道当前的流水号?对于Generator来说这样合适吗?
  4. 既然TConstantCodeSerialNumberGenerator表示固定代码,那调用NextSerialNumber方法是不是很奇怪?
  5. 如果考虑在流水号中加入日期的话,这个模型需要怎么修改?

除了有这些疑问以外,恐怕这个模型存在最大的问题在于:它看上去更像是一种技术模型——虽然能勉强工作,但是没有表现任何领域知识

现在,我们该停下来,回到起点,重新思考一下:

What's the Problem?

 

1. 领域知识

我们要解决的问题其实很简单——就是要获取一个可用的编号(Number)编号一般是有几部分(Part)组成的。比如某张入库单的编号”RK200901160001”就包含下面3个部分:

  1. 代码:“RK”
  2. 日期:“20090116”
  3. 流水号:“0001”

其中,代码是固定不变的,流水号会自动递增,日期一般是当前系统日期(固定格式,比如YYYYMM、YYYYMMDD),另外当日期变化时再重置流水号。写到这里,我们终于找到了一个重要概念:编号规则(Number Rule)。编号规则定义了多个连续的段(Number Part),各段组合起来就生成了一个编号。正如下图所示:

领域驱动设计实践——流水号生成器(下)_第2张图片

图2 分析模型

在实际的应用当中,流水号规则可能很复杂,也许要支持数字(如‘0000’-‘9999’)、英文字母(如‘A' -  'Z'),甚至是一些自定义的字符(如‘0’-‘Z’)的组合。既然这样,我们可以提取一个抽象概念:序列Sequence)。如在卡号规则当中规定遇4跳过等等就表示卡号是由除4以外的其他阿拉伯数字组成的序列。{ 序列可以考虑用任意进制的计算器来实现:) }

 

2. 领域模型

结合上面的领域知识,我设计了新的领域模型:

领域驱动设计实践——流水号生成器(下)_第3张图片

图2 编号规则领域模型(V2)

我们来看看客户是如何使用这个模型的:

Code

(呵呵,现在是不是感觉NumberRule比之前的SerialNumberGenerator贴切多了?)

接下来我们再简单看看TNumberRule的实现:

1. 设置规则

Code

 

 

2. 生成编号

Code

P.S. 序列部分(TSequenceNumberPart)的核心功能实现委托给任意进制计算器(BaseNCalculator),具体可参考源代码。

 

3. 业务应用

为了实现具体的业务应用,我们还需要做两件事:

1. 编号规则的持久化(一般使用XML,暂省略)

2. 编号的获取和更新

我们可以在业务层定义了下面两个接口,方便供客户使用:

Code

Code

我们只需要通过访问一个全局的Factory/Registry来获得一个当前Context的INumberGenerator实例,然后调用NextNumber方法就可以获取编号。其实现可参考:

Code

INumberCalculator接口主要针对那些手工输入编号或需要进行统计编号数量的应用(比如,输入开始卡号和结束卡号,自动计算数量)。


最后,期待大家的批评和指点。

 

P.S. NumberGenerator项目及源代码(Delphi 2009,含Test Case)下载地址:http://files.cnblogs.com/baoquan/NumberGenerator.rar

 

作者:左保权 (Zuo Baoquan)
Blog:
http://baoquan.cnblogs.com/
MSN:Baoquan.Zuo[at]hotmail.com
欢迎转载,欢迎讨论,欢迎指正。

你可能感兴趣的:(设计)