随笔 - 13  文章 - 0  评论 - 70  阅读 - 43498 

今天在CSDN上逛的时候,我突然看到一个提问帖子

问一个大家一个问题
一个字符串 加1 谁做过
例如KA001A001
下一个就是 KA001A002
到 KA001Z999
就 KA002A001

这顿时引起了我的兴趣。流水号(Serial Number)在程序中应用很普遍,生成规则也各不相同。(比如,我们公司的会员卡卡号规则里面就有一个“卡号遇4跳过”的选项。)我上Google简单搜了一下,发现都是硬编码的函数,虽然它们能解决具体的问题,但不够通用灵活,换个应用场景又需要重写代码。那有没有一种简单、通用又灵活的流水号生成器呢?今天就让我们一起来试试。

流水号一般都是固定长度,由几部分组合而成:

  • 日期(如:20090101)
  • 常量代码(如:KA)
  • 数字序列(如:0001-9999)
  • 字母序列(如:A-Z)
  • 特殊字符(如:-)

简单分析之后,我们先定义一个接口(Delphi):


1 ISerialNumberGenerator = interface
2   function NextSerialNumber(const serialNumber: string): string;
3   function Validate(const serialNumber: string
): Boolean;
4 end 

ISerialNumberGenerator接口主要有两个作用(职责):

  1. 生成下一个可用的流水号(NextSerialNumber)
  2. 验证某个流水号是否合法(Validate)

OK,接下来我们先列个简单的任务列表:

任务列表

  1. 支持可循环的数字序列('001’-'999’)
  2. 支持可循环的字母序列('A’-'Z’)
  3. 支持常量代码('KA’)
  4. 支持字母序列和数字序列组合(KA001A001)

 再写一个简单的测试用例(Test Case):

复制代码

1 procedure TTestNumbericSerialNumberGenerator.SetUp;
2 begin
3   fGenerator := TNumbericSerialNumberGenerator.Create('001', '999');
4 end;
5 
6 procedure TTestNumbericSerialNumberGenerator.TestNextSerialNumber;
7 begin
9     CheckEquals('002', fGenerator.NextSerialNumber('001'));
10   CheckEquals('010', fGenerator.NextSerialNumber('009'));
11   CheckEquals('011', fGenerator.NextSerialNumber('010'));
12   CheckEquals('100', fGenerator.NextSerialNumber('099'));
13   CheckEquals('999', fGenerator.NextSerialNumber('998'));
14   CheckEquals('001', fGenerator.NextSerialNumber('999'));
复制代码

 运行Test Case,编译器提示TNumbericSerialNumberGenerator没有定义,我们一起来实现它:

TNumbericSerialNumberGenerator Class Interface

TNumbericSerialNumberGenerator

再运行,Test Case通过!第一个任务完成了。注意上面代码中的repeated,当流水号到了结束值时,应递进更高位。

任务列表

  1. 支持可循环的数字序列('001’-'999’)
  2. 支持可循环的字母序列('A’-'Z’)
  3. 支持常量代码('KA’)
  4. 支持字母序列和数字序列组合(KA001A001)

接下来我们分别实现字母序列和常量代码流水号:

复制代码

1 procedure TTestLetterSerialNumberGenerator.TestNextSerialNumber;
2 var
3   letter: Char;
4 begin
5   for letter := 'A' to 'Y' do
6   begin
7     CheckEquals(Chr(Ord(letter) + 1), fGenerator.NextSerialNumber(letter));
8   end
9   CheckEquals('A', fGenerator.NextSerialNumber('Z'));
10 end
复制代码

 

TLetterSerialNumberGenerator

任务列表

  1. 支持可循环的数字序列('001’-'999’)
  2. 支持可循环的字母序列('A’-'Z’)
  3. 支持常量代码('KA’)
  4. 支持字母序列和数字序列组合(KA001A001)


1 procedure TTestConstantCodeSerialNumberGenerator.TestNextSerialNumber;
2 begin
3   CheckEquals('KA', fGenerator.NextSerialNumber('KA'));
4 end

 

TConstantCodeSerialNumberGenerator

任务列表

  1. 支持可循环的数字序列('001’-'999’)
  2. 支持可循环的字母序列('A’-'Z’)
  3. 支持常量代码('KA’)
  4. 支持字母序列和数字序列组合(KA001A001)

呵呵,到了高潮部分了,我们先写一段测试案例来测试组合流水号:

复制代码

1 procedure TTestCompositeSerialNumberGenerator.SetUp;
2 begin
3   inherited;
4   fGenerator := TSerialNumberGenerator.Create([
5     TConstantCodeSerialNumberGenerator.Create('KA'),
6     TNumbericSerialNumberGenerator.Create('001', '999'),
7     TLetterSerialNumberGenerator.Create,
8     TNumbericSerialNumberGenerator.Create('001', '999')
9   ]);
10 end;
11 
12 procedure TTestCompositeSerialNumberGenerator.TestNextSerialNumber;
13 begin
14   CheckEquals('KA001A002', fGenerator.NextSerialNumber('KA001A001'));
15   CheckEquals('KA001B001', fGenerator.NextSerialNumber('KA001A999'));
16   CheckEquals('KA001Z002', fGenerator.NextSerialNumber('KA001Z001'));
17   CheckEquals('KA002A001', fGenerator.NextSerialNumber('KA001Z999'));
18 end
复制代码

再实现TSerialNumberGenerator:

TSerialNumberGenerator

 我们来想想TSerialNumberGenerator的这两个方法应该如何实现。我们只要运用组合模式(Composite Pattern),把serialNumber拆分开来,并委托给相应的Generator实例处理就好了。我们需要再调整一下ISerialNumberGenerator 接口:

复制代码

1 ISerialNumberGenerator = interface
2   function NextSerialNumber(const serialNumber: string): string; overload;
3   function NextSerialNumber(const serialNumber: string; var repeated: Boolean): string; overload;
4   function Validate(const serialNumber: string): Boolean;
5   function GetTotalLength: Integer;
6   property TotalLength: Integer read GetTotalLength;
7 end
复制代码

 

TSerialNumberGenerator

 再运行Test Case,呵呵,绿色进度条: )

image

写到这里,我们已经成功了一半了。接下来,希望大家提出批评意见,我将继续重构代码。下集将更加精彩,敬请关注:)

image

posted on   保权  阅读(4247)  评论(12编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述

website tracker

点击右上角即可分享
微信分享提示