今天在CSDN上逛的時候,我突然看到一個提問帖子:
問一個大家一個問題
一個字符串 加1 誰做過
例如KA001A001
下一個就是 KA001A002
到 KA001Z999
就 KA002A001
這頓時引起了我的興趣。流水號(Serial Number)在程序中應用很普遍,生成規則也各不相同。(比如,我們公司的會員卡卡號規則裡面就有一個“卡號遇4跳過”的選項。)我上Google簡單搜了一下,發現都是硬編碼的函數,雖然它們能解決具體的問題,但不夠通用靈活,換個應用場景又需要重寫代碼。那有沒有一種簡單、通用又靈活的流水號生成器呢?今天就讓我們一起來試試。
流水號一般都是固定長度,由幾部分組合而成:
日期(如:20090101)
常量代碼(如:KA)
數字序列(如:0001-9999)
字母序列(如:A-Z)
特殊字符(如:-)
簡單分析之後,我們先定義一個接口(Delphi):
ISerialNumberGenerator
1ISerialNumberGenerator =interface
2 functionNextSerialNumber(const serialNumber: string): string;
3 functionValidate(const serialNumber: string): Boolean;
4end
ISerialNumberGenerator接口主要有兩個作用(職責):
生成下一個可用的流水號(NextSerialNumber)
驗證某個流水號是否合法(Validate)
OK,接下來我們先列個簡單的任務列表:
任務列表
支持可循環的數字序列('001’-'999’)
支持可循環的字母序列('A’-'Z’)
支持常量代碼('KA’)
支持字母序列和數字序列組合(KA001A001)
再寫一個簡單的測試用例(Test Case):
TTestNumbericSerialNumberGenerator
1procedure TTestNumbericSerialNumberGenerator.SetUp;
2begin
3 fGenerator := TNumbericSerialNumberGenerator.Create('001', '999');
4end;
5
6procedure TTestNumbericSerialNumberGenerator.TestNextSerialNumber;
7begin
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
1TNumbericSerialNumberGenerator =class(TInterfacedObject, ISerialNumberGenerator)
2private
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;
9end
TNumbericSerialNumberGenerator
1constructor TNumbericSerialNumberGenerator.Create(const beginSerialNumber, endSerialNumber: string);
2begin
3 inherited Create;
4 fBeginSerialNumber := beginSerialNumber;
5 fEndSerialNumber := endSerialNumber;
6 fTotalLength := Length(beginSerialNumber);
7end;
8
9function TNumbericSerialNumberGenerator.NextSerialNumber(
10 const serialNumber: string): string;
11var
12 value: Int64;
13 repeated: Boolean;
14begin
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;
28end;
29
30function TNumbericSerialNumberGenerator.Validate(
31 const serialNumber: string): Boolean;
32var
33 i: Integer;
34begin
35 Result := False;
36 if Length(serialNumber) = fTotalLength then
37 begin
38 for i :=1to fTotalLength do
39 begin
40 ifnot (AnsiChar(serialNumber[i]) in ['0'..'9']) then
41 begin
42 Break;
43 end;
44 end;
45 Result := True;
46 end;
47end;
再運行,Test Case通過!第一個任務完成了。注意上面代碼中的repeated,當流水號到了結束值時,應遞進更高位。
任務列表
支持可循環的數字序列('001’-'999’)
支持可循環的字母序列('A’-'Z’)
支持常量代碼('KA’)
支持字母序列和數字序列組合(KA001A001)
接下來我們分別實現字母序列和常量代碼流水號:
TTestLetterSerialNumberGenerator
1procedure TTestLetterSerialNumberGenerator.TestNextSerialNumber;
2var
3 letter: Char;
4begin
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'));
10end
任務列表
支持可循環的數字序列('001’-'999’)
支持可循環的字母序列('A’-'Z’)
支持常量代碼('KA’)
支持字母序列和數字序列組合(KA001A001)
TTestConstantCodeSerialNumberGenerator
1procedure TTestConstantCodeSerialNumberGenerator.TestNextSerialNumber;
2begin
3 CheckEquals('KA', fGenerator.NextSerialNumber('KA'));
4end
任務列表
支持可循環的數字序列('001’-'999’)
支持可循環的字母序列('A’-'Z’)
支持常量代碼('KA’)
支持字母序列和數字序列組合(KA001A001)
呵呵,到了高潮部分了,我們先寫一段測試案例來測試組合流水號:
TTestCompositeSerialNumberGenerator
1procedure TTestCompositeSerialNumberGenerator.SetUp;
2begin
3 inherited;
4 fGenerator := TSerialNumberGenerator.Create([
5 TConstantCodeSerialNumberGenerator.Create('KA'),
6 TNumbericSerialNumberGenerator.Create('001', '999'),
7 TLetterSerialNumberGenerator.Create,
8 TNumbericSerialNumberGenerator.Create('001', '999')
9 ]);
10end;
11
12procedure TTestCompositeSerialNumberGenerator.TestNextSerialNumber;
13begin
14 CheckEquals('KA001A002', fGenerator.NextSerialNumber('KA001A001'));
15 CheckEquals('KA001B001', fGenerator.NextSerialNumber('KA001A999'));
16 CheckEquals('KA001Z002', fGenerator.NextSerialNumber('KA001Z001'));
17 CheckEquals('KA002A001', fGenerator.NextSerialNumber('KA001Z999'));
18end
再實現TSerialNumberGenerator:
TSerialNumberGenerator
1constructor TSerialNumberGenerator.Create(
2 const generators: arrayof ISerialNumberGenerator);
3var
4 i: Integer;
5begin
6 inherited Create;
7 fList := TInterfaceList.Create;
8 for i :=0to High(generators) do
9 begin
10 fList.Add(generators[i]);
11 end;
12end;
13
14destructor TSerialNumberGenerator.Destroy;
15begin
16 fList.Free;
17 inherited;
18end;
19
20function TSerialNumberGenerator.DoNextSerialNumber(
21 const serialNumber: string): string;
22begin
23
24end;
25
26function TSerialNumberGenerator.Validate(const serialNumber: string): Boolean;
27var
28 i: Integer;
29begin
30 for i :=0to fList.Count -1do
31 begin
32
33 end;
34end
我們來想想TSerialNumberGenerator的這兩個方法應該如何實現。我們只要運用組合模式(Composite Pattern),把serialNumber拆分開來,並委托給相應的Generator實例處理就好了。我們需要再調整一下ISerialNumberGenerator 接口:
ISerialNumberGenerator
1ISerialNumberGenerator =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;
7end
TSerialNumberGenerator
1function TSerialNumberGenerator.DoNextSerialNumber(
2 const serialNumber: string; var repeated: Boolean): string;
3var
4 generator: ISerialNumberGenerator;
5 sn: string;
6 pos: Integer;
7 i: Integer;
8 list: TStrings;
9begin
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 :=0to fGenerators.Count -1do
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 -1downto0do
26 begin
27 generator := ISerialNumberGenerator(fGenerators[i]);
28 list[i] := generator.NextSerialNumber(list[i], repeated);
29 ifnot repeated then Break;
30 end;
31 for i :=0to list.Count -1do
32 begin
33 Result := Result + list[i];
34 end;
35 finally
36 list.Free;
37 end;
38end;
39
40function TSerialNumberGenerator.Validate(const serialNumber: string): Boolean;
41var
42 generator: ISerialNumberGenerator;
43 sn: string;
44 pos: Integer;
45 i: Integer;
46begin
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 :=0to fGenerators.Count -1do
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 ifnot Result then Break;
60 end;
61end
再運行Test Case,呵呵,綠色進度條: )
寫到這裡,我們已經成功了一半了。接下來,希望大家提出批評意見,我將繼續重構代碼。下集將更加精彩,敬請關注:)