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

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

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

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


Java 流水号生成器 流水号生成器实现_流水号Java 流水号生成器 流水号生成器实现_流水号_02ISerialNumberGenerator

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):

Java 流水号生成器 流水号生成器实现_流水号Java 流水号生成器 流水号生成器实现_流水号_02TTestNumbericSerialNumberGenerator

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没有定义,我们一起来实现它:


Java 流水号生成器 流水号生成器实现_流水号Java 流水号生成器 流水号生成器实现_流水号_02TNumbericSerialNumberGenerator Class Interface

1 TNumbericSerialNumberGenerator = class(TInterfacedObject, ISerialNumberGenerator)
2 private
3   fBeginSerialNumber, fEndSerialNumber: string;
4   fLength: Integer;
5 public
6   constructor Create(const beginSerialNumber, endSerialNumber: string);
7   function NextSerialNumber(const serialNumber: string): string;
8   function Validate(const serialNumber: string): Boolean;
9 end


Java 流水号生成器 流水号生成器实现_流水号Java 流水号生成器 流水号生成器实现_流水号_02TNumbericSerialNumberGenerator

1 constructor TNumbericSerialNumberGenerator.Create(const beginSerialNumber, endSerialNumber: string);
 2 begin
 3   inherited Create;
 4   fBeginSerialNumber := beginSerialNumber;
 5   fEndSerialNumber := endSerialNumber;
 6   fTotalLength := Length(beginSerialNumber);
 7 end;
 8 
 9 function TNumbericSerialNumberGenerator.NextSerialNumber(
10   const serialNumber: string): string;
11 var
12   value: Int64;
13   repeated: Boolean;
14 begin
15   CheckSerialNumber(serialNumber);
16   repeated := serialNumber = fEndSerialNumber;
17   if repeated then
18   begin
19     Result := fBeginSerialNumber;
20   end
21   else
22   begin
23     value := StrToInt64(serialNumber);
24     Inc(value);
25     Result := IntToStr(value);
26     Result := StringOfChar('0', fTotalLength - Length(Result)) + Result;
27   end;
28 end;
29 
30 function TNumbericSerialNumberGenerator.Validate(
31   const serialNumber: string): Boolean;
32 var
33   i: Integer;
34 begin
35   Result := False;
36   if Length(serialNumber) = fTotalLength then
37   begin
38     for i := 1 to fTotalLength do
39     begin
40       if not (AnsiChar(serialNumber[i]) in ['0'..'9']) then
41       begin
42         Break;
43       end;
44     end;
45     Result := True;
46   end;
47 end;


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

任务列表

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

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

Java 流水号生成器 流水号生成器实现_流水号Java 流水号生成器 流水号生成器实现_流水号_02TTestLetterSerialNumberGenerator

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

 

Java 流水号生成器 流水号生成器实现_流水号Java 流水号生成器 流水号生成器实现_流水号_02TLetterSerialNumberGenerator


任务列表

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

Java 流水号生成器 流水号生成器实现_流水号Java 流水号生成器 流水号生成器实现_流水号_02TTestConstantCodeSerialNumberGenerator

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

 

Java 流水号生成器 流水号生成器实现_流水号Java 流水号生成器 流水号生成器实现_流水号_02TConstantCodeSerialNumberGenerator


任务列表

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

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

Java 流水号生成器 流水号生成器实现_流水号Java 流水号生成器 流水号生成器实现_流水号_02TTestCompositeSerialNumberGenerator

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:

Java 流水号生成器 流水号生成器实现_流水号Java 流水号生成器 流水号生成器实现_流水号_02TSerialNumberGenerator

1 constructor TSerialNumberGenerator.Create(
2   const generators: array of ISerialNumberGenerator);
3 var
4   i: Integer;
5 begin
6   inherited Create;
7   fList := TInterfaceList.Create;
8   for i := 0 to High(generators) do
9   begin
10     fList.Add(generators[i]);
11   end;
12 end;
13 
14 destructor TSerialNumberGenerator.Destroy;
15 begin
16   fList.Free;
17   inherited;
18 end;
19 
20 function TSerialNumberGenerator.DoNextSerialNumber(
21   const serialNumber: string): string;
22 begin 
23 
24 end;
25 
26 function TSerialNumberGenerator.Validate(const serialNumber: string): Boolean;
27 var
28   i: Integer;
29 begin
30   for i := 0 to fList.Count - 1 do
31   begin
32 
33   end;
34 end


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

Java 流水号生成器 流水号生成器实现_流水号Java 流水号生成器 流水号生成器实现_流水号_02ISerialNumberGenerator

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

 

Java 流水号生成器 流水号生成器实现_流水号Java 流水号生成器 流水号生成器实现_流水号_02TSerialNumberGenerator

1 function TSerialNumberGenerator.DoNextSerialNumber(
2   const serialNumber: string; var repeated: Boolean): string;
3 var
4   generator: ISerialNumberGenerator;
5   sn: string;
6   pos: Integer;
7   i: Integer;
8   list: TStrings;
9 begin
10   if Length(serialNumber) <> fTotalLength then
11   begin
12     raise ESerialNumberException.CreateFmt(SIllegalSerialNumber, [serialNumber]);
13   end;
14   pos := 1;
15   list := TStringList.Create;
16   try
17     for i := 0 to fGenerators.Count - 1 do
18     begin
19       generator := ISerialNumberGenerator(fGenerators[i]);
20       sn := MidStr(serialNumber, pos, generator.TotalLength);
21       list.Add(sn);
22       Inc(pos, generator.TotalLength);
23     end;
24     repeated := False;
25     for i := list.Count - 1 downto 0 do
26     begin
27       generator := ISerialNumberGenerator(fGenerators[i]);
28       list[i] := generator.NextSerialNumber(list[i], repeated);
29       if not repeated then Break;
30     end;
31     for i := 0 to list.Count - 1 do
32     begin
33       Result := Result + list[i];
34     end;
35   finally
36     list.Free;
37   end;
38 end;
39 
40 function TSerialNumberGenerator.Validate(const serialNumber: string): Boolean;
41 var
42   generator: ISerialNumberGenerator;
43   sn: string;
44   pos: Integer;
45   i: Integer;
46 begin
47   if Length(serialNumber) <> fTotalLength then
48   begin
49     raise ESerialNumberException.CreateFmt(SIllegalSerialNumber, [serialNumber]);
50   end;
51   Result := True;
52   pos := 1;
53   for i := 0 to fGenerators.Count - 1 do
54   begin
55     generator := ISerialNumberGenerator(fGenerators[i]);
56     sn := MidStr(serialNumber, pos, generator.TotalLength);
57     Inc(pos, generator.TotalLength);
58     Result := generator.Validate(sn);
59     if not Result then Break;
60   end;
61 end


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

Java 流水号生成器 流水号生成器实现_Java 流水号生成器_25

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

Java 流水号生成器 流水号生成器实现_Test_26