首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> Delphi

領域驅動設計實踐:流水號生成器(上)——TDD 從零構建組合模式生成器

     編輯:Delphi

源於 CSDN 一個「字符串加 1」的提問:KA001A001 → KA001A002 → ... → KA001Z999 → KA002A001。本文展示如何用測試驅動開發(TDD)逐步構建一個通用、靈活的流水號生成器,並引入組合模式處理多段組合。

1. 核心介面定義

ISerialNumberGenerator = interface
  function NextSerialNumber(const SerialNumber: string): string;
  function Validate(const SerialNumber: string): Boolean;
end;

職責:生成下一個可用流水號、驗證合法性。

2. 任務清單

  1. 支援可循環數字序列('001'-'999')
  2. 支援可循環字母序列('A'-'Z')
  3. 支援常量代碼('KA')
  4. 支援字母+數字組合(KA001A001)

3. 第一輪:數字序列生成器

測試用例

procedure TTestNumericSerialNumberGenerator.SetUp;
begin
  fGenerator := TNumericSerialNumberGenerator.Create('001', '999');
end;

procedure TTestNumericSerialNumberGenerator.TestNextSerialNumber;
begin
  CheckEquals('002', fGenerator.NextSerialNumber('001'));
  CheckEquals('010', fGenerator.NextSerialNumber('009'));
  CheckEquals('100', fGenerator.NextSerialNumber('099'));
  CheckEquals('999', fGenerator.NextSerialNumber('998'));
  CheckEquals('001', fGenerator.NextSerialNumber('999'));  { 循環 }
end;

實現

type
  TNumericSerialNumberGenerator = class(TInterfacedObject, ISerialNumberGenerator)
  private
    fBegin, fEnd: string;
    fLength: Integer;
  public
    constructor Create(const BeginSN, EndSN: string);
    function NextSerialNumber(const SN: string): string;
    function Validate(const SN: string): Boolean;
  end;

constructor TNumericSerialNumberGenerator.Create(const BeginSN, EndSN: string);
begin
  inherited Create;
  fBegin := BeginSN; fEnd := EndSN; fLength := Length(BeginSN);
end;

function TNumericSerialNumberGenerator.NextSerialNumber(const SN: string): string;
var
  Val: Int64; Repeated: Boolean;
begin
  CheckSerialNumber(SN);
  Repeated := (SN = fEnd);
  if Repeated then
    Result := fBegin
  else
  begin
    Val := StrToInt64(SN);
    Inc(Val);
    Result := IntToStr(Val);
    Result := StringOfChar('0', fLength - Length(Result)) + Result;
  end;
end;

function TNumericSerialNumberGenerator.Validate(const SN: string): Boolean;
var I: Integer;
begin
  Result := False;
  if Length(SN) = fLength then
  begin
    for I := 1 to fLength do
      if not (SN[I] in ['0'..'9']) then Exit;
    Result := True;
  end;
end;

4. 第二/三輪:字母序列與常量代碼

{ 字母序列 }
TLetterSerialNumberGenerator = class(TInterfacedObject, ISerialNumberGenerator)
  function NextSerialNumber(const SN: string): string;
  function Validate(const SN: string): Boolean;
end;

function TLetterSerialNumberGenerator.NextSerialNumber(const SN: string): string;
var C: Char;
begin
  C := SN[1];
  if C = 'Z' then Result := 'A' else Result := Chr(Ord(C) + 1);
end;

{ 常量代碼 }
TConstantCodeSerialNumberGenerator = class(TInterfacedObject, ISerialNumberGenerator)
  function NextSerialNumber(const SN: string): string;
  function Validate(const SN: string): Boolean;
end;

function TConstantCodeSerialNumberGenerator.NextSerialNumber(const SN: string): string;
begin
  Result := SN;  { 常量不變 }
end;

5. 第四輪:組合生成器(組合模式)

測試用例

procedure TTestCompositeSerialNumberGenerator.SetUp;
begin
  fGenerator := TSerialNumberGenerator.Create([
    TConstantCodeSerialNumberGenerator.Create('KA'),
    TNumericSerialNumberGenerator.Create('001', '999'),
    TLetterSerialNumberGenerator.Create,
    TNumericSerialNumberGenerator.Create('001', '999')
  ]);
end;

procedure TTestCompositeSerialNumberGenerator.TestNextSerialNumber;
begin
  CheckEquals('KA001A002', fGenerator.NextSerialNumber('KA001A001'));
  CheckEquals('KA001B001', fGenerator.NextSerialNumber('KA001A999'));
  CheckEquals('KA001Z002', fGenerator.NextSerialNumber('KA001Z001'));
  CheckEquals('KA002A001', fGenerator.NextSerialNumber('KA001Z999'));  { 多級進位 }
end;

介面擴展:支援進位旗標

ISerialNumberGenerator = interface
  function NextSerialNumber(const SN: string): string; overload;
  function NextSerialNumber(const SN: string; var Repeated: Boolean): string; overload;
  function Validate(const SN: string): Boolean;
  function GetTotalLength: Integer;
  property TotalLength: Integer read GetTotalLength;
end;

組合生成器實現

type
  TSerialNumberGenerator = class(TInterfacedObject, ISerialNumberGenerator)
  private
    fGenerators: TInterfaceList;
    fTotalLength: Integer;
  public
    constructor Create(const Generators: array of ISerialNumberGenerator);
    destructor Destroy; override;
    function NextSerialNumber(const SN: string): string; overload;
    function NextSerialNumber(const SN: string; var Repeated: Boolean): string; overload;
    function Validate(const SN: string): Boolean;
    function GetTotalLength: Integer;
  end;

function TSerialNumberGenerator.NextSerialNumber(const SN: string; var Repeated: Boolean): string;
var
  Gen: ISerialNumberGenerator;
  Part: string;
  Pos: Integer;
  I: Integer;
  List: TStringList;
begin
  if Length(SN) <> fTotalLength then
    raise ESerialNumberException.CreateFmt(SIllegalSerialNumber, [SN]);

  Pos := 1;
  List := TStringList.Create;
  try
    { 1. 拆分 }
    for I := 0 to fGenerators.Count - 1 do
    begin
      Gen := ISerialNumberGenerator(fGenerators[I]);
      Part := Copy(SN, Pos, Gen.TotalLength);
      List.Add(Part);
      Inc(Pos, Gen.TotalLength);
    end;

    { 2. 從低位向高位遞增 }
    Repeated := False;
    for I := List.Count - 1 downto 0 do
    begin
      Gen := ISerialNumberGenerator(fGenerators[I]);
      List[I] := Gen.NextSerialNumber(List[I], Repeated);
      if not Repeated then Break;  { 無進位則停止 }
    end;

    { 3. 組合 }
    Result := '';
    for I := 0 to List.Count - 1 do
      Result := Result + List[I];
  finally
    List.Free;
  end;
end;

function TSerialNumberGenerator.Validate(const SN: string): Boolean;
var
  Gen: ISerialNumberGenerator;
  Part: string;
  Pos, I: Integer;
begin
  if Length(SN) <> fTotalLength then
    raise ESerialNumberException.CreateFmt(SIllegalSerialNumber, [SN]);
  Result := True;
  Pos := 1;
  for I := 0 to fGenerators.Count - 1 do
  begin
    Gen := ISerialNumberGenerator(fGenerators[I]);
    Part := Copy(SN, Pos, Gen.TotalLength);
    Inc(Pos, Gen.TotalLength);
    Result := Gen.Validate(Part);
    if not Result then Break;
  end;
end;

6. 運行測試:綠燈通過

綠色進度條

7. 反思與下一步

雖然 V1 模型已能工作,但存在疑問:

  • ISerialNumberGenerator 與抽象基類職責重疊?
  • Validate 返回 Boolean 是否足夠?
  • 調用者必須傳入當前號碼,生成器無狀態,是否合適?
  • 常量代碼段呼叫 NextSerialNumber 語義怪異?
  • 日期段如何支援?
  • 模型未表達領域知識(編號規則、段、序列)。

下篇將回到領域知識,重構為真正的領域模型:NumberRule。敬請期待!

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved