程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> 領域驅動設計實踐——流水號生成器(上)

領域驅動設計實踐——流水號生成器(上)

編輯:Delphi

今天在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,呵呵,綠色進度條: )

寫到這裡,我們已經成功了一半了。接下來,希望大家提出批評意見,我將繼續重構代碼。下集將更加精彩,敬請關注:)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved