![]() |
Delphi-Version: 5
Generisches Visitor-Pattern
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:
Das funktioniert wie gewünscht.
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; 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:
Dafür müsste ich allerdings TNode.Accept auch generisch machen, was nicht erlaubt ist weil die Methode auch virtuell ist:
TSumVisitor = class(TVisitor<Double>)
public procedure Visit(Node: TNodeA; out Result: Double); override; procedure Visit(Node: TNodeB; out Result: Double); override; end;
Code:
Hat jemand eine Idee wie man das lösen kann, so dass man verschiedene Typen zurückgeben kann?
TNode = class
public procedure Accept<T>(Visitor: TVisitor<T>; out Result: T); virtual; abstract; ... end; 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). |
AW: Generisches Visitor-Pattern
Ich habe es jetzt nicht im Detail komplett nachvollzogen, aber hast Du Dir mal
![]() |
AW: Generisches Visitor-Pattern
Danke, kenne den Post, hilft aber leider nicht weiter.
|
AW: Generisches Visitor-Pattern
Ich sehe aktuell keinen Grund, das Ganze generisch zu machen. Es ist nur so, daß du das Pattern nicht korrekt umgesetzt hast und deswegen die Probleme mit dem Typ des result-Werts kommst.
So solltest die niemals
Delphi-Quellcode:
aufrufen, sondern immer
t.Accept(Visitor)
Delphi-Quellcode:
. Entsprechend muss das Accept gar nichts über den Result wissen, den es ja eh nur an den Visitor weiterreicht.
Visitor.Visit(t)
Eine brauchbare, allerdings so auf die Schnelle hingeschriebene, Implementierung angelehnt an Part 3 der oben genannten Artikelserie wäre sowas:
Delphi-Quellcode:
type
TVisitor = class(TInterfacedPersistent) end; TNode = class strict private FChildren: TObjectList<TNode>; function GetChild(Index: Integer): TNode; function GetChildCount: Integer; public constructor Create; destructor Destroy; override; procedure Accept(Visitor: TVisitor); 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: TVisitor); override; property Value: Integer read FValue; end; TNodeB = class(TNode) strict private FValue: Double; public constructor Create(Value: Double); procedure Accept(Visitor: TVisitor); override; property Value: Double read FValue; end; INodeAVisitor = interface ['{290FB9EF-9F7E-4008-9ECA-C52169D200E0}'] procedure VisitNodeA(Instance: TNodeA); end; INodeBVisitor = interface ['{C893648E-FCA0-4FB2-9400-2AFE3B862256}'] procedure VisitNodeB(Instance: TNodeB); end; TNodeVisitor = class(TVisitor, INodeAVisitor, INodeBVisitor) protected procedure VisitNodeA(Node: TNodeA); virtual; abstract; procedure VisitNodeB(Node: TNodeB); virtual; abstract; public procedure Visit(ANode: TNode); virtual; end; TConcatVisitor = class(TNodeVisitor) private FList: TStringList; function GetResultValue: string; protected procedure VisitNodeA(Node: TNodeA); override; procedure VisitNodeB(Node: TNodeB); override; public constructor Create; destructor Destroy; override; property ResultValue: string read GetResultValue; end;
Delphi-Quellcode:
{ 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: TVisitor); var vis: INodeAVisitor; begin if Supports(Visitor, INodeAVisitor, vis) then vis.VisitNodeA(Self); end; constructor TNodeA.Create(Value: Integer); begin inherited Create; FValue := Value; end; { TNodeB } procedure TNodeB.Accept(Visitor: TVisitor); var vis: INodeBVisitor; begin if Supports(Visitor, INodeBVisitor, vis) then vis.VisitNodeB(Self); end; constructor TNodeB.Create(Value: Double); begin inherited Create; FValue := Value; end; procedure TNodeVisitor.Visit(ANode: TNode); begin ANode.Accept(Self); end; constructor TConcatVisitor.Create; begin inherited Create; FList := TStringList.Create(); end; destructor TConcatVisitor.Destroy; begin FList.Free; inherited Destroy; end; function TConcatVisitor.GetResultValue: string; begin Result := FList.Text; end; procedure TConcatVisitor.VisitNodeA(Node: TNodeA); var i: Integer; res: string; saveList: TStringList; begin saveList := FList; try FList := TStringList.Create; try FList.Add(IntToStr(Node.Value)); for i := 0 to Node.ChildCount - 1 do begin Visit(Node.Children[i]); end; res := string.Join(sLineBreak, FList.ToStringArray); finally FList.Free; end; finally FList := saveList; end; FList.Add(res); end; procedure TConcatVisitor.VisitNodeB(Node: TNodeB); var i: Integer; res: string; saveList: TStringList; begin saveList := FList; try FList := TStringList.Create; try FList.Add(FloatToStr(Node.Value)); for i := 0 to Node.ChildCount - 1 do begin Visit(Node.Children[i]); end; res := string.Join(', ', FList.ToStringArray); finally FList.Free; end; finally FList := saveList; end; FList.Add(res); end;
Delphi-Quellcode:
procedure TForm495.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 ConcatVisitor.Visit(t); s := ConcatVisitor.ResultValue; finally ConcatVisitor.Free; end; ShowMessage(s); finally t.Free; end; end; |
AW: Generisches Visitor-Pattern
Zitat:
Delphi-Quellcode:
TGenericNode<T> = class(TNode)
strict private FValue: T; public constructor Create(Value: T); property Value: T read FValue; end; TNodeA = class(TGenericNode<Integer>) public procedure Accept(Visitor: TVisitor); override; end; TNodeB = class(TGenericNode<Double>) public procedure Accept(Visitor: TVisitor); override; end; |
AW: Generisches Visitor-Pattern
Zitat:
![]()
Code:
Siehe auch die Methode "Visit(Addition addition)" die "addition.Left.Accept" aufruft.
e.Accept(expressionPrinter);
Zitat:
Zitat:
Mein Minimalbeispiel kann man natürlich so umschreiben, (auch wenn Interfaces jetzt unter Delphi nicht sehr praktisch sind) das ist schon klar. Aber das Ziel ist es das Visitorpattern so zu erweitern, dass man zusätzliche Parameter/Rückgabewerte haben kann. Das ist sehr nützlich um kompakten Code zu schreiben, der den Stack ausnutzt/Rekursion. Wenn ich das ganze explizit verwalten muss, analog zu einer StringList, wird es sehr unelegant. |
AW: Generisches Visitor-Pattern
Das C# Beispiel betrachte ich mal als gutes Beispiel, wie man es nicht machen sollte. Nur weil C# davor steht, ist der Code ja nicht automatisch über jeden Zweifel erhaben.
Zitat:
Zitat:
|
AW: Generisches Visitor-Pattern
Zitat:
Was ist denn der praktische Vorteil deiner Lösung, also was "So solltest die niemals t.Accept(Visitor) aufrufen, sondern immer Visitor.Visit(t)" angeht? Inwiefern entkoppelt es mehr da man sich ja in beiden Fällen auf eine abstrakte Klasse/Interface bezieht? Zitat:
Hier gibt es ähnliche Fragen: ![]() ![]() Zitat:
Im Endeffekt sollte etwas wie die Lösung für Format() gut funktionieren:
Delphi-Quellcode:
Den Rückgabewert könnte man dann über eine Instanz-Variable des Visitors lösen, wie in deinem Vorschlag, und auch in dem C++-Beispiel oben. Im Endeffekt ist das dann eine Art Callbackfunktion (die ja auch häufig zusätzliche "User"-Parameter haben).
procedure Accept(Visitor: TVisitor; const Args: array of const);
Damit wäre ich eigentlich zufrieden. Bleibt die obige Frage übrig. |
AW: Generisches Visitor-Pattern
Hallo,
ohne mich jetzt mit dem Code tief beschäftigt zu haben Zitat:
|
AW: Generisches Visitor-Pattern
Zitat:
Zitat:
Hier (wieder) mal ein schnelles Beispiel basierend auf dem vorigen Code (Create, Destroy und GetResultValue entsprechen TContentVisitor). Es wird einfach die Baumstruktur mit entsprechenden Einrückungen und Präfix dargestellt. Dabei werden die Node-Typen unterschiedlich formatiert. Man sieht hier auch deutlich den Vorteil der virtuellen Visit Methode gegenüber einem Aufruf von Accept:
Delphi-Quellcode:
type
TIndentVisitor = class(TNodeVisitor) private FLevel: Integer; FList: TStringList; FPrefix: string; function GetResultValue: string; protected procedure VisitNodeA(Node: TNodeA); override; procedure VisitNodeB(Node: TNodeB); override; public constructor Create; destructor Destroy; override; procedure Visit(ANode: TNode); override; property ResultValue: string read GetResultValue; end;
Delphi-Quellcode:
procedure TIndentVisitor.Visit(ANode: TNode);
var i: Integer; savePrefix: string; begin savePrefix := FPrefix; inherited; // hier wird implizit das Accept aufgerufen, was auf die jeweilige VisitNodeA/B Methode verzweigt FList.Add(StringOfChar(' ', 2*FLevel) + FPrefix); Inc(FLevel); for i := 0 to ANode.ChildCount - 1 do begin Visit(ANode.Children[i]); end; Dec(FLevel); FPrefix := savePrefix; end; procedure TIndentVisitor.VisitNodeA(Node: TNodeA); begin FPrefix := FPrefix + '.' + Node.ClassName; end; procedure TIndentVisitor.VisitNodeB(Node: TNodeB); begin FPrefix := FPrefix + '.[' + Node.ClassName + ']'; end; |
Alle Zeitangaben in WEZ +1. Es ist jetzt 01:12 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz