今天在CSDN上逛的时候,我突然看到一个提问帖子:
问一个大家一个问题
一个字符串 加1 谁做过
例如KA001A001
下一个就是 KA001A002
到 KA001Z999
就 KA002A001
这顿时引起了我的兴趣。流水号(Serial Number)在程序中应用很普遍,生成规则也各不相同。(比如,我们公司的会员卡卡号规则里面就有一个“卡号遇4跳过”的选项。)我上Google简单搜了一下,发现都是硬编码的函数,虽然它们能解决具体的问题,但不够通用灵活,换个应用场景又需要重写代码。那有没有一种简单、通用又灵活的流水号生成器呢?今天就让我们一起来试试。
流水号一般都是固定长度,由几部分组合而成:
-
日期(如:20090101)
-
常量代码(如:KA)
-
数字序列(如:0001-9999)
-
字母序列(如:A-Z)
- 特殊字符(如:-)
简单分析之后,我们先定义一个接口(Delphi):
ISerialNumberGenerator
1 ISerialNumberGenerator = interface
2 function NextSerialNumber(const serialNumber: string): string;
3 function Validate(const serialNumber: string): Boolean;
4 end
ISerialNumberGenerator接口主要有两个作用(职责):
-
生成下一个可用的流水号(NextSerialNumber)
- 验证某个流水号是否合法(Validate)
OK,接下来我们先列个简单的任务列表:
任务列表
-
支持可循环的数字序列('001’-'999’)
-
支持可循环的字母序列('A’-'Z’)
-
支持常量代码('KA’)
- 支持字母序列和数字序列组合(KA001A001)
再写一个简单的测试用例(Test Case):
TTestNumbericSerialNumberGenerator
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
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
TNumbericSerialNumberGenerator
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,当流水号到了结束值时,应递进更高位。
任务列表
-
支持可循环的数字序列('001’-'999’)
-
支持可循环的字母序列('A’-'Z’)
-
支持常量代码('KA’)
- 支持字母序列和数字序列组合(KA001A001)
接下来我们分别实现字母序列和常量代码流水号:
TTestLetterSerialNumberGenerator
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
任务列表
-
支持可循环的数字序列('001’-'999’)
-
支持可循环的字母序列('A’-'Z’)
-
支持常量代码('KA’)
-
支持字母序列和数字序列组合(KA001A001)
TTestConstantCodeSerialNumberGenerator
1 procedure TTestConstantCodeSerialNumberGenerator.TestNextSerialNumber;
2 begin
3 CheckEquals('KA', fGenerator.NextSerialNumber('KA'));
4 end
TConstantCodeSerialNumberGenerator
任务列表
-
支持可循环的数字序列('001’-'999’)
-
支持可循环的字母序列('A’-'Z’)
-
支持常量代码('KA’)
-
支持字母序列和数字序列组合(KA001A001)
呵呵,到了高潮部分了,我们先写一段测试案例来测试组合流水号:
TTestCompositeSerialNumberGenerator
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
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 接口:
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
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,呵呵,绿色进度条: )
写到这里,我们已经成功了一半了。接下来,希望大家提出批评意见,我将继续重构代码。下集将更加精彩,敬请关注:)