Einzelnen Beitrag anzeigen

Benutzerbild von mael
mael

Registriert seit: 13. Jan 2005
391 Beiträge
 
Delphi XE3 Professional
 
#1

Generisches Visitor-Pattern

  Alt 11. Mär 2020, 10:16
Delphi-Version: 5
Hallo,

Ich versuche ein etwas modifiziertes Visitor-Pattern mit Generics auszustatten.

Hier mal ein minimales Beispiel ohne Generics (bis auf TObjectList<TNode>, aber das ist hier nicht relevant):

Code:
type
  TStringVisitor = class;

  TNode = class
  strict private
    FChildren: TObjectList<TNode>;
    function GetChild(Index: Integer): TNode;
    function GetChildCount: Integer;
  public
    constructor Create;
    destructor Destroy; override;

    procedure Accept(Visitor: TStringVisitor; out Result: string); virtual; abstract;

    procedure AddChild(Node: TNode);

    property ChildCount: Integer read GetChildCount;
    property Children[Index: Integer]: TNode read GetChild;
  end;

  TNodeA = class(TNode)
  strict private
    FValue: Integer;
  public
    constructor Create(Value: Integer);

    procedure Accept(Visitor: TStringVisitor; out Result: string); override;

    property Value: Integer read FValue;
  end;

  TNodeB = class(TNode)
  strict private
    FValue: Double;
  public
    constructor Create(Value: Double);

    procedure Accept(Visitor: TStringVisitor; out Result: string); override;

    property Value: Double read FValue;
  end;

  TStringVisitor = class
  public
    procedure Visit(Node: TNodeA; out Result: string); overload; virtual; abstract;
    procedure Visit(Node: TNodeB; out Result: string); overload; virtual; abstract;
  end;

  TConcatVisitor = class(TStringVisitor)
  public
    procedure Visit(Node: TNodeA; out Result: string); override;
    procedure Visit(Node: TNodeB; out Result: string); override;
  end;



{ TNode }

procedure TNode.AddChild(Node: TNode);
begin
  FChildren.Add(Node);
end;

constructor TNode.Create;
begin
  inherited Create;
  FChildren := TObjectList<TNode>.Create(True);
end;

destructor TNode.Destroy;
begin
  FChildren.Free;
  inherited;
end;

function TNode.GetChild(Index: Integer): TNode;
begin
  Result := FChildren[Index];
end;

function TNode.GetChildCount: Integer;
begin
  Result := FChildren.Count;
end;

{ TNodeA }

procedure TNodeA.Accept(Visitor: TStringVisitor; out Result: string);
begin
  Visitor.Visit(Self, Result);
end;

constructor TNodeA.Create(Value: Integer);
begin
  inherited Create;
  FValue := Value;
end;

{ TNodeB }

procedure TNodeB.Accept(Visitor: TStringVisitor; out Result: string);
begin
  Visitor.Visit(Self, Result);
end;

constructor TNodeB.Create(Value: Double);
begin
  inherited Create;
  FValue := Value;
end;

{ TConcatVisitor }

procedure TConcatVisitor.Visit(Node: TNodeA; out Result: string);
var
  i: Integer;
  ChildRes: string;
begin
  Result := IntToStr(Node.Value);

  for i := 0 to Node.ChildCount - 1 do
  begin
    Node.Children[i].Accept(Self, ChildRes);

    Result := Result + SLineBreak + Childres;
  end;
end;

procedure TConcatVisitor.Visit(Node: TNodeB; out Result: string);
var
  i: Integer;
  ChildRes: string;
begin
  Result := FloatToStr(Node.Value);

  for i := 0 to Node.ChildCount - 1 do
  begin
    Node.Children[i].Accept(Self, ChildRes);

    Result := Result + ', ' + Childres;
  end;
end;

procedure TForm2.FormCreate(Sender: TObject);
var
  t: TNode;
  s: string;
  ConcatVisitor: TConcatVisitor;
begin
  t := TNodeA.Create(10);
  try
    t.AddChild(TNodeB.Create(2.5));

    ConcatVisitor := TConcatVisitor.Create;
    try
      t.Accept(ConcatVisitor, s);
    finally
      ConcatVisitor.Free;
    end;

    ShowMessage(s);
  finally
    t.Free;
  end;
end;
Das funktioniert wie gewünscht.

Aber mal angenommen ich will einen zweiten Visitor machen, der ein Double zurückgibt (die Summe aller Werte der Knoten), müsste ich so etwas verwenden:

Code:
  TSumVisitor = class(TVisitor<Double>)
  public
    procedure Visit(Node: TNodeA; out Result: Double); override;
    procedure Visit(Node: TNodeB; out Result: Double); override;
  end;
Dafür müsste ich allerdings TNode.Accept auch generisch machen, was nicht erlaubt ist weil die Methode auch virtuell ist:

Code:
  TNode = class
  public
    procedure Accept<T>(Visitor: TVisitor<T>; out Result: T); virtual; abstract;
  ...
  end;
Hat jemand eine Idee wie man das lösen kann, so dass man verschiedene Typen zurückgeben kann?
Typecasting eines Pointers würde natürlich gehen, möchte ich aber wegen Typsicherheit vermeiden.

P.S.: Meine Delphi-Version ist XE3, keine Ahnung warum hier 5 steht (nie gehabt).
HxD, schneller Hexeditor:
http://mh-nexus.de/hxd

Geändert von mael (11. Mär 2020 um 10:26 Uhr)
  Mit Zitat antworten Zitat