Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Visitor Pattern (https://www.delphipraxis.net/152968-visitor-pattern.html)

hansmaad 15. Jul 2010 06:48

Delphi-Version: 2010

Visitor Pattern
 
Guten morgen,

ich versuche gerade meinen ersten Delphi Visitor zu schreiben und bin natürlich auf die wohl bekannten zirkulären Probleme gestoßen. Ich habe mit der Suche einige Themen gefunden, wo darüber gesprochen wird, eine Lösung aber nur erwähnt wird.
Da ich ein recht umfangreiches Composite habe, möchte ich auf keinen Fall alles in eine Datei packen. Ich wär daher daran interessiert ob es überhaupt eine Lösung dazu gibt und wie diese aussieht.

Delphi-Quellcode:
unit Base;
interface
type
    Visitor = class;
    Base = class
    public
      procedure Accept(v : Visitor);virtual;abstract;
    end;

    A = class(Base)
    public
      procedure Accept(v : Visitor);override;
    end;

    B = class(Base)
    public
      procedure Accept(v : Visitor);override;
    end;

    Visitor = class
    public
        procedure Visit(a : A); overload;
        procedure Visit(b : B); overload;
    end;

implementation

{ Visitor }
procedure Visitor.Visit(a: A);
begin
    WriteLn('A');
end;

procedure Visitor.Visit(b: B);
begin
    WriteLn('B');
end;

{ B }
procedure B.Accept(v: Visitor);
begin
    v.Visit(Self);
end;

{ A }
procedure A.Accept(v: Visitor);
begin
    v.Visit(Self);
end;

end.
Delphi-Quellcode:
var
    list : TList<Base>;
    v : Visitor;
    x : Base;
begin
    list := TList<Base>.Create;
    v := Visitor.Create;
    list.Add(A.Create);
    list.Add(B.Create);

    for x in list do
        x.Accept(v);

    list.Free;
    v.Free;
    ReadLn;
end.
A soll nun in UnitA, B in UnitB und Visitor in UnitVisitor.
Wie löst ihr sowas? Setzt ihr vielleicht auch irgendwelche sinnvollen Alternativen zu Visitor ein? Ich seh bei mir irgendwie nur die Möglichkeit alles in A und B zu implementieren und sie zu Gottklassen mutieren zu lassen.

GRüße
hansmaad

mkinzler 15. Jul 2010 06:54

AW: Visitor Pattern
 
Funktioniert nur wenn alle Klassen in der selben Unit deklariert werden.
Aber warum benötigst du eigentlich die Typen A,B?

himitsu 15. Jul 2010 07:01

AW: Visitor Pattern
 
Delphi-Quellcode:
Visitor = class
public
  procedure Visit(a : A); overload;
  procedure Visit(b : B); overload;
end;
Wieso 2 verschiedene Methoden, wo sie doch genau das Selbe machen?

Delphi-Quellcode:
Visitor = class
public
  procedure Visit(b: Base);
end;

procedure Visitor.Visit(b: Base);
begin
  WriteLn(b.ClassName);
end;
Und schon muß Visitor und Base deine A und B nicht kennen und schon kannst du Visitor und Base in eine Unit packen und A, sowie B lassen sich in anderen Units unterbringen.

"x" kann als Basisklasse ja die beiden abgeleiteten Klassen/Objekte entgegennehmen.

Es muß sich also in der Hauptunit alles nur mit der Basisklasse erledigen lassen und das Klassenspezifische muß halt überschrieben und von den Subklassen erledigt werden.




Aber ansonsten hast du (wie Markus schon sagte) keine Chance und mußt alles in der selben Unit verpacken.

hansmaad 15. Jul 2010 07:19

AW: Visitor Pattern
 
Zitat:

Zitat von himitsu (Beitrag 1035371)
Wieso 2 verschiedene Methoden, wo sie doch genau das Selbe machen?

Sie machen doch nicht das selbe?! Die eine schreibt 'A', die andere schreibt 'B'.
Das ist doch der Sinn des Vistor Patterns. Für jedes ConcreteElement gibt es unterschiedliches Verhalten, dass aber nicht in der Element Klasse, sondern in einem (konkreten) Visitor implementiert ist.


Meine jetzige Alternativlösung ist folgende:

Delphi-Quellcode:
unit UBase;
uses UWriter;
type
    Base = class
    public
      function MakeWriter(): Writer; virtual; abstract;
    end;
Delphi-Quellcode:
unit UWriter;
interface
type
    Writer = class
    public
        procedure Write();virtual;abstract;
    end;
Delphi-Quellcode:
unit UA;

interface
uses
    UBase,UWriter;

type
    A = class(Base)
    public
        function MakeWriter() : Writer ;override;
    end;

implementation
uses
    UAWriter;

{ AA }

function A.MakeWriter: Writer;
begin
    Result := AWriter.Create(Self);
end;
Delphi-Quellcode:
unit UAWriter;
interface
uses
    UWriter,
    UA;

type
    AWriter = class(Writer)
    public
        constructor Create(x : A);overload;
        procedure Write();override;
    end;

implementation

{ AWriter }

procedure AWriter.Write;
begin
    WriteLn('A');
end;

end.
Das hat gegenüber Visitor den Nachteil, dass ich bei neuer Funktionalität in jeder Elementklasse(A, B...) eine neue Schnittstelle hinzufügen muss, statt nur einen neuen Visitor abzuleiten. Der Nutzen hängt natürlich immer vom Verhältnis Elemente:Vistors ab und was sich wie häufig ändert. Wenn häufig Elemente hinzukommen, kann Visitor auch recht lästig sein.

Mir scheint Visitor in Delphi allerdings sehr aufwändig, wenn nicht unmöglich zu implementieren zu sein.

Zum konkreten Fall:
Base, A, B .. sind Elemente eines Composite (Teile einer Anlage). Stellt euch das WriteLn('A') als Schreiben eines seitenlangen Berichts vor. Von solchen Sachen gibt es ein paar...Dinge, die man möglichst von A, B usw. trennen möchte.

mkinzler 15. Jul 2010 07:22

AW: Visitor Pattern
 
Zitat:

Das ist doch der Sinn des Vistor Patterns. Für jedes ConcreteElement gibt es unterschiedliches Verhalten, dass aber nicht in der Element Klasse, sondern in einem (konkreten) Visitor implementiert ist.
da sie sich aber im Interface nicht unterscheiden, kannst du der Visitorklasse auch Elemente vom (abstrakten) übergelagerten Typ (Base) übergeben.

hansmaad 15. Jul 2010 07:27

AW: Visitor Pattern
 
okok... das Beispiel war wohl zu minimiert.
Delphi-Quellcode:
unit Base;
interface
type
    Visitor = class;
    Base = class
    public
      procedure Accept(v : Visitor);virtual;abstract;
    end;

    A = class(Base)
    public
      procedure Accept(v : Visitor);override;
      function AZeugs() : String;
    end;

    B = class(Base)
    public
      procedure Accept(v : Visitor);override;
      function BZeugs() : String;
    end;

    Visitor = class
    public
        procedure Visit(a : A); overload;
        procedure Visit(b : B); overload;
    end;

implementation

{ Visitor }
procedure Visitor.Visit(a: A);
begin
    WriteLn(a.AZeugs());
end;

procedure Visitor.Visit(b: B);
begin
    WriteLn(b.BZeugs());
end;

//...

end.

himitsu 15. Jul 2010 07:53

AW: Visitor Pattern
 
Also doch das Gleiche. :gruebel:

Wenn es aufgeteilt werden soll, dann mußt man es auf eine gemeinsame Basis (Base) bringen.
Delphi-Quellcode:
unit BaseUnit;

interface

  type
    Visitor = class;
    Base = class
    public
      procedure Accept(v : Visitor); virtual; abstract;
      function Zeugs() : String; virtual; abstract;
    end;

    Visitor = class
    public
      procedure Visit(b : Base);
    end;

implementation

  { Visitor }
  procedure Visitor.Visit(b: Base);
  begin
    WriteLn(b.Zeugs());
  end;

  //...

end.
Delphi-Quellcode:
unit AUnit;

interface
  uses
    BaseUnit;

  type
    A = class(Base)
    public
      procedure Accept(v : Visitor); override;
      function Zeugs() : String; override;
    end;

implementation
  ...
Delphi-Quellcode:
unit BUnit;

interface
  uses
    BaseUnit;

  type
    B = class(Base)
    public
      procedure Accept(v : Visitor); override;
      function Zeugs() : String; override;
    end;

implementation
  ...

hansmaad 15. Jul 2010 08:04

AW: Visitor Pattern
 
grrr...
Delphi-Quellcode:
Schiff = class(Fahrzeug)
public
  // ...
  property Kabinen : Integer read Kabinenanzahl;
end
Delphi-Quellcode:
Auto = class(Fahrzeug)
public
  // ...
  property Reifen: Integer read Reifenanzahl;
end
Delphi-Quellcode:
procedure ReportVisitor.Visit(s : Schiff);
begin
  WriteLn('Das Schiff hat ' + s.Kabinen + ' Kabinen');
  // 100 weitere Schiffsdaten
end;
procedure ReportVisitor.Visit(a : Auto);
begin
  WriteLn('Das Auto hat ' + s.Reifen+ ' Räder');
  // 100 weitere Autodaten
end;

Nun glaubt mir mal, dass das hier ein potentieller Anwendungsfall des Visitor Musters ist.

himitsu 15. Jul 2010 08:26

AW: Visitor Pattern
 
Wenn es aufgeteilt werden soll und sich dann nicht alle Klassen gegenseitig kennen können, dann muß man sich einfach auf eine gemeinsame Basis beschränken, welche sich kennt und wovon dann alles Andere/Unbekannte abgeleitet wird.

Ich denka mal das letze Beispiel dürfte eher dem Pattern entsprechen.
Da gibt es dann passende Visitor, die wissen was zu machen ist
und welche über einer gemeinsame Basis aufrufbar sind.

Delphi-Quellcode:
unit BaseUnit;

interface
  type
    Visitor = class;
    Base = class
    public
      procedure Accept(v : Visitor); virtual; abstract;
      function Visit(v : Visitor) : Integer; virtual; abstract;
    end;

    Visitor = class
    public
      procedure Visit(b : Base);
    end;

implementation

  { Visitor }
  procedure Visitor.Visit(b : Base);
  begin
    WriteLn(b.Visit(self));
  end;

  //...

end.

////////////////////////////////////

Schiff = class(Base)
public
  procedure Accept(v : Visitor); override;
  function Visit(v : Visitor) : Integer; override;
  // ...
  property Kabinen : Integer read Kabinenanzahl;
end;

function Schiff.Visit(v : Visitor) : Integer;
begin
  if v is Base then
    Result := Kabinen
  else
    ...
end;

Wobei man auch auf Seiten des Visitors aufsplitten könnte, aber das wäre dann weniger flexibel.
Delphi-Quellcode:
unit BaseUnit;

interface

  type
    Visitor = class;
    Base = class
    public
      procedure Accept(v : Visitor); virtual; abstract;
    end;

    StrBase = class(Base)
    public
      function StrVisit() : String; virtual; abstract;
    end;

    IntBase = class(Base)
    public
      function IntVisit() : Integer; virtual; abstract;
    end;

    Visitor = class
    public
      procedure Visit(b : Base);
    end;

implementation

  { Visitor }
  procedure Visitor.Visit(b: Base);
  begin
    if b is StrBase then
      WriteLn(b.StrVisit);
    else if b is IntBase then
      WriteLn(b.IntVisit);
  end;

  //...

end.
oder
Delphi-Quellcode:
unit BaseUnit;

interface

  type
    TVisitType = (vtInt, vtStr);
    Visitor = class;
    Base = class
    public
      procedure Accept(v : Visitor); virtual; abstract;
      funktion GetVisitType: TVisitType; virtual; abstract;
      function IntVisit() : Integer; virtual; {abstract;}
      function StrVisit() : String; virtual; {abstract;}
      // {...} vielleicht einen leeren Dummy verwenden,
      // damit man nicht überall alles überschreiben muß
    end;

    Visitor = class
    public
      procedure Visit(b : Base);
    end;

implementation

  { Visitor }
  procedure Visitor.Visit(b: Base);
  begin
    case b.GetVisitType of
      vtInt: WriteLn(b.IntVisit);
      vtStr: WriteLn(b.StrVisit);
      else ...
    end;
  end;

  //...

end.

Eventuell kannst du auch den Visitor aufteilen.
Da kennt dann jeder Visitornachfahre seine Base-Class und weiß was mit ihr zu machen ist.
Delphi-Quellcode:
unit BaseUnit;

interface
  type
    Visitor = class;
    Base = class
    public
      procedure Accept(v : Visitor); virtual; abstract;
      procedure Visit(v : Visitor); virtual; abstract;
    end;

    Visitor = class
    public
      procedure Visit(b : Base);
    end;

implementation

  { Visitor }
  procedure Visitor.Visit(b : Base);
  begin
    b.Visit(self);
  end;

  //...

end.

///////////////////////////////////////////////////////

unit StrBaseUnit;

interface
  uses
    BaseUnit;

  type
    StrBase = class()Base
    public
      procedure Accept(v : Visitor); override;
      procedure StrVisit; virtual; abstract;
    end;

    StrVisitor = class(Visitor)
    public
      procedure StrVisit(b : Base);
    end;

implementation

  { Visitor }
  procedure StrVisitor.StrVisit(b : Base);
  begin
    WriteLn((b as StrBase).StrVisit);
  end;

  //...

end.

hansmaad 15. Jul 2010 08:53

AW: Visitor Pattern
 
Also danke für deine Mühen, aber 1. versteh ich nicht alles und 2. hat das denke ich nicht mehr viel mit Visitor zu tun.
Code:
procedure Visit(b : Base);
und eine Typunterscheidung in if else, switch case oder Zugriff über die gemeinsame Schnittstelle ist keine Alternative. Dafür brauche ich keinen Visitor.
Da kann ich auch direkt
Delphi-Quellcode:
for base in Liste do
begin
  if base is Dies : Tu dies
  if base is Das : Tu das
  ...
end;
oder
Delphi-Quellcode:
for base in Liste do
begin
  TuWasFuerAlleBaseGilt(base);
end;
Wenn es mit dem Visitor nicht geht, werde ich eher einen Weg wie in #4 gehen. Ob der Visitor überhaupt das richtige Muster ist müsste ich eh noch entscheiden. Ich wollte nur vorher mal sehen, ob es sich überhautpt lohnt darüber nachzudenken, oder ob es in Delphi einfach quasi unmöglich ist.

Blup 15. Jul 2010 09:00

AW: Visitor Pattern
 
Keine Ahnung ob dir das hilft:
Delphi-Quellcode:
unit Base;

type
  TVisistor = class;

  TBase = class
  public
    procedure Accept(v : TVisitor); virtual; abstract;
  end;

  TVisitor = class
  public
    procedure Visit(ABase: TBase); virtual; abstract;
  end;
Delphi-Quellcode:
unit ClassA;

uses
  Base;

type
  TA = class(TBase)
  public
    procedure Accept(v : TVisitor); override;
  end;
Delphi-Quellcode:
unit ClassB;

uses
  Base;

type
  TB = class(TBase)
  public
    procedure Accept(v : TVisitor); override;
  end;
Delphi-Quellcode:
unit VisitorAB;

uses
  Base, ClassA, ClassB;

type
  TVisitorAB
  private
    procedure VisitA(A: TA);
    procedure VisitB(B: TB);
  public
    procedure Visit(ABase: TBase); override;
  end;

implementation
 
procedure TVisitorAB.Visit(ABase: TBase);
begin
  if ABase is TA then
    VisitA(TA(ABase));
  if ABase is TB then
    VisitB(TB(ABase));
end;
Statt die Funktionen für die konkreten Klassen im Visitor zu implementieren, könnte man diese auch in spezille Klassen auslagern, die beim Visitor mit der jeweils unterstützten Baseklasse registriert werden.

himitsu 15. Jul 2010 09:10

AW: Visitor Pattern
 
Zitat:

Zitat von hansmaad (Beitrag 1035396)
und eine Typunterscheidung in if else, switch case oder Zugriff über die gemeinsame Schnittstelle ist keine Alternative. Dafür brauche ich keinen Visitor.
Da kann ich auch direkt ...

Das kommt drauf an ... irgendo muß ja unterschieden werden, wenn es mehrere Möglichkeiten gibt, da man sich nicht auf eine Basis einigen konnte.

Aber der Vorteil mit dem "Visitor" wäre dann, daß diese Unterscheidung dort implementiert ist und man sich extern nicht darum kümmern muß
Ich denke mal, das soll wohl der Grund für dieses Pattern sein? :gruebel:

Stell dir mal vpr du rufst dieses an meheren Stellen auf, dann müßtest du, bei Einfügen einer neuen Klasse (Base-Nachfahre) an allen Stellen dieses anpassen,
anstatt z.B. nur einen passenden Visitor-Nachfahren zu erstellen (falls ein Anderer Visitor es nicht schon kann).

hansmaad 15. Jul 2010 09:33

AW: Visitor Pattern
 
Zitat:

Zitat von himitsu (Beitrag 1035405)
...irgendo muß ja unterschieden werden,...

->Polymorphie

Zitat:

Zitat von himitsu (Beitrag 1035405)
Stell dir mal vpr du rufst dieses an meheren Stellen auf, dann müßtest du, bei Einfügen einer neuen Klasse (Base-Nachfahre) an allen Stellen dieses anpassen,
anstatt z.B. nur einen passenden Visitor-Nachfahren zu erstellen (falls ein Anderer Visitor es nicht schon kann).

Richtig, das hatte ich ja schon auf der ersten Seite erwähnt. Wenn es häufig neue Elementklassen gibt (Base-Nachfahren) ist Visitor schlecht. Auf dieser Seite ist das System nciht "offen für Erweiterungen".
Wenn allerdings meine Elementklassenhierachie ein fertiges System ist, dass nicht mehr um neue Elemente erweitert wird, aber die Funktionalität dieser Elemente häufig erweitert wird, ist eine Lösung mit Visitor sehr wohl "offen für Erweiterungen". In diesem Fall bleibt nämlich die gesamte Hierachie wie sie ist und man leitet nur einen neuen Visitor von der Visitor Basisklasse ab.

hansmaad 15. Jul 2010 09:55

AW: Visitor Pattern
 
Wie es scheint, kommt man um hässliche Typecasts nicht herum. Ich hab mir noch eine Lösung geschrieben, die diese Typunterscheidung wenigstens an einer Stelle kapselt und ein normales Visitor 'faked':

Delphi-Quellcode:
unit UBase;
interface

type
    BaseVisitor = class;

    Base = class
    public
      procedure Accept(v : BaseVisitor);virtual;abstract;
    end;

    BaseVisitor = class
    public
        procedure Visit(b : Base); virtual; abstract;
    end;
implementation
end.
Delphi-Quellcode:
unit UnitA;
interface
uses
    UBase;

type
    A = class(Base)
        Socke_ : Integer;
    public
        procedure Accept(v : BaseVisitor);override;
        property Socke : Integer read Socke_;
    end;

implementation

{ A }
procedure A.Accept(v: BaseVisitor);
begin
    v.Visit(Self);
end;

end.
Delphi-Quellcode:
unit UnitB;
interface
uses
    UBase;
type
    B = class(Base)
    private
        Schnitzel_ :String;
    public
        procedure Accept(v : BaseVisitor);override;
        property Schnitzel : String read Schnitzel_;
    end;

implementation

{ B }
procedure B.Accept(v: BaseVisitor);
begin
    v.Visit(Self);
end;

end.
Delphi-Quellcode:
unit UVisitor;

interface
uses
    UBase,
    UConcreteVisitor;
type
    Visitor = class(BaseVisitor)
    private
        V_ : ConcreteVisitor;
    public
        constructor Create(v : ConcreteVisitor);overload;
        destructor Destroy();override;
        procedure Visit(el : Base); override;
    end;

implementation
uses
    SysUtils,
    UnitA,
    UnitB;

{ Visitor }

constructor Visitor.Create(v: ConcreteVisitor);
begin
  inherited Create;
  V_ := v;
end;

destructor Visitor.Destroy;
begin
  V_.Free;
  inherited;
end;

procedure Visitor.Visit(el: Base);
begin
    // Die gekapselte Ekelstelle:
    if el is A then
        V_.Visit(A(el))
    else if el is B then
        V_.Visit(B(el))
    else
        Raise Exception.Create('Unknown Element!');
end;

end.
Delphi-Quellcode:
unit UConcreteVisitor;

interface

uses
    UnitA,UnitB;
type

    ConcreteVisitor = class
    public
        procedure Visit(x : A);overload;virtual;abstract;
        procedure Visit(x : B);overload;virtual;abstract;
    end;

implementation
end.
Delphi-Quellcode:
unit UWriteVisitor;

interface
uses
    UConcreteVisitor,
    UnitA,
    UnitB;
type
    WriteVisitor = class(ConcreteVisitor)
    public
        procedure Visit(x : A);overload;override;
        procedure Visit(x : B);overload;override;
    end;
implementation
uses
    SysUtils;

{ WriterVisitor }

procedure WriteVisitor.Visit(x: A);
begin
    WriteLn('Ein A! mit ' + IntToStr(x.Socke))
end;

procedure WriteVisitor.Visit(x: B);
begin
    WriteLn('BeeeeH! :>>> ' + x.Schnitzel)
end;

end.
Delphi-Quellcode:
program visit;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Generics.Collections,
  UBase in 'UBase.pas',
  UVisitor in 'UVisitor.pas',
  UConcreteVisitor in 'UConcreteVisitor.pas',
  UnitA in 'UnitA.pas',
  UnitB in 'UnitB.pas',
  UWriteVisitor in 'UWriteVisitor.pas';

var
    list : TList<Base>;
    v : BaseVisitor;
    x : Base;
begin
    list := TList<Base>.Create;
    list.Add(A.Create);
    list.Add(B.Create);

    v := Visitor.Create(WriteVisitor.Create);

    for x in list do
        x.Accept(v);

    list.Free;
    v.Free;

    ReadLn;
end.
Neue Visitor erben von ConcreteVisitor und können wie 'normale' Visitor geschrieben werden.

Uwe Raabe 15. Jul 2010 10:18

AW: Visitor Pattern
 
Das ganze läßt sich allerdings viel eleganter, flexibler und nebenbei noch typsicher mit Interfaces realisieren. Dabei können die Basis-Klasse und die abgeleiteten Klassen in unterschiedlichen Units liegen und brauchen über den konkreten Visitor gar nichts wissen. Der Visitor muss natütlich alle Klassen kennen, die er besuchen will.

Delphi-Quellcode:
unit uVisitorTypes;

interface

type
  IVisitor = interface
    ['{59A6BE7C-6BD7-4746-AA37-F42DCB6D8D01}']
    procedure Visit(Instance: IInterface);
    procedure NotSupported(const Name: string);
  end;

  IVisited = interface
    ['{6C55ECC2-E1AB-43DD-96EC-755FF5C12400}']
    procedure Accept(Visitor: IVisitor);
  end;

implementation

end.
Delphi-Quellcode:
unit uBase;

interface

uses
  Classes, uVisitorTypes;

type
  { wahlweise auch von TInterfacedObject ableiten }
  TBase = class(TInterfacedPersistent, IVisited)
  protected
    procedure Accept(Visitor: IVisitor); virtual;
  public
  end;

  IBaseVisitor = interface
    ['{ACD6D82F-8C09-4B13-833B-9C653B99E050}']
    procedure VisitBase(Instance: TBase);
  end;

implementation

uses
  SysUtils;

procedure TBase.Accept(Visitor: IVisitor);
var
  MyVisitor: IBaseVisitor;
begin
  if Supports(Visitor, IBaseVisitor, MyVisitor) then
    MyVisitor.VisitBase(Self)
  else
    Visitor.NotSupported(ClassName);
end;

end.
Delphi-Quellcode:
unit unitA;

interface

uses
  uBase, uVisitorTypes;

type
  TA = class(TBase)
  private
    FAZeugs: string;
  protected
    procedure Accept(Visitor: IVisitor); override;
  public
    property AZeugs: string read FAZeugs write FAZeugs;
  end;

  IAVisitor = interface
    ['{3CEF1431-741C-4482-AC9C-1C787DE003C9}']
    procedure VisitA(Instance: TA);
  end;

implementation

uses
  SysUtils;

procedure TA.Accept(Visitor: IVisitor);
var
  MyVisitor: IAVisitor;
begin
  if Supports(Visitor, IAVisitor, MyVisitor) then
    MyVisitor.VisitA(Self)
  else
    Visitor.NotSupported(ClassName);
end;

end.
Delphi-Quellcode:
unit unitB;

interface

uses
  uBase, uVisitorTypes;

type
  TB = class(TBase)
  private
    FBZeugs: string;
  protected
    procedure Accept(Visitor: IVisitor); override;
  public
    property BZeugs: string read FBZeugs write FBZeugs;
  end;

  IBVisitor = interface
    ['{801E4A2D-74B6-49FC-8BC6-DCB995AF25F6}']
    procedure VisitB(Instance: TB);
  end;

implementation

uses
  SysUtils;

procedure TB.Accept(Visitor: IVisitor);
var
  MyVisitor: IBVisitor;
begin
  if Supports(Visitor, IBVisitor, MyVisitor) then
    MyVisitor.VisitB(Self)
  else
    Visitor.NotSupported(ClassName);
end;

end.
Delphi-Quellcode:
unit uVisitor;

interface

uses
  uVisitorTypes, unitA, unitB;

type
  { wahlweise auch von TInterfacedPersistent ableiten }
  TVisitor = class(TInterfacedObject, IVisitor, IAVisitor, IBVisitor)
  protected
    procedure NotSupported(const Name: string);
    procedure VisitA(Instance: TA);
    procedure VisitB(Instance: TB);
  public
    procedure Visit(Instance: IInterface);
  end;

implementation

uses
  SysUtils;

procedure TVisitor.NotSupported(const Name: string);
begin
  // nach Bedarf ignorieren oder Fehler melden
end;

procedure TVisitor.Visit(Instance: IInterface);
var
  visited: IVisited;
begin
  if Supports(Instance, IVisited, visited) then
    visited.Accept(Self)
  else
    // nach Bedarf ignorieren oder Fehler melden
end;

procedure TVisitor.VisitA(Instance: TA);
begin
  Writeln(Instance.AZeugs);
end;

procedure TVisitor.VisitB(Instance: TB);
begin
  Writeln(Instance.BZeugs);
end;

end.

hansmaad 15. Jul 2010 10:39

AW: Visitor Pattern
 
Ok, das sieht ja ganz nett aus (mal davon abgesehen, dass Interfaces für meinen Geschmack generell furchtbar aussehen:wink:).

das IBaseVisitor Interface kann ich mir bei einer abstrakten Basisklasse schenken, da es keine Base Instanzen gibt, korrekt?

TVisitor.VisitA(Instance: TA);
TVisitor.VisitB(Instance: TB);

würde ich wohl abstrakt machen und konkrete Besucher von TVisitor ableiten. Das wäre dann im Stil 'Template Methode'. Dieser Supports / Not Supported Kram wird von der Basisklasse behandelt, der konkrete Besucher kümmert sich nur noch um A und B.

Auf den ersten Blick scheint mir das eine ganz brauchbare Lösung zu sein :thumb:
Ich werde das mal ausprobieren,
Danke!

Blup 15. Jul 2010 10:59

AW: Visitor Pattern
 
Im Normalfall Interface-Parameter besser als const deklarieren.
Sonst erfolgt unnötige Referenzzählung (wie auch bei string).
Delphi-Quellcode:
type
  IVisitor = interface
    ['{59A6BE7C-6BD7-4746-AA37-F42DCB6D8D01}']
    procedure Visit(const Instance: IInterface);
    procedure NotSupported(const Name: string);
  end;

  IVisited = interface
    ['{6C55ECC2-E1AB-43DD-96EC-755FF5C12400}']
    procedure Accept(const Visitor: IVisitor);
  end;

Uwe Raabe 15. Jul 2010 11:16

AW: Visitor Pattern
 
Zitat:

Zitat von hansmaad (Beitrag 1035427)
das IBaseVisitor Interface kann ich mir bei einer abstrakten Basisklasse schenken, da es keine Base Instanzen gibt, korrekt

Das hängt davon ab, was dein Visitor tun soll. So könnte ein Visitor, der lediglich auf TBase-Eigenschaften und Methoden zugreift, auch nur das IBaseVisitor-Interface implementieren und die IAVisitor- und IBVisitor-Interfaces gar nicht implementieren. Ergänzend dazu muss man dann allerdings die Implementationen von Accept in TA und TB leicht abwandeln.

Delphi-Quellcode:
procedure TA.Accept(Visitor: IVisitor);
var
  MyVisitor: IAVisitor;
begin
  if Supports(Visitor, IAVisitor, MyVisitor) then
    MyVisitor.VisitA(Self)
  else
    inherited;
end;
Delphi-Quellcode:
procedure TB.Accept(Visitor: IVisitor);
var
  MyVisitor: IBVisitor;
begin
  if Supports(Visitor, IBVisitor, MyVisitor) then
    MyVisitor.VisitB(Self)
  else
    inherited;
end;
Wenn nun der Visitor das passende Interface nicht implementiert, wird einfach die Accept-Methode des Vorfahren aufgerufen.

Delphi-Quellcode:
unit uBaseVisitor;

interface

uses
  Classes, uVisitorTypes, uBase;

type
  TBaseVisitor = class(TInterfacedObject, IVisitor, IBaseVisitor)
  protected
    procedure NotSupported(const Name: string);
    procedure VisitBase(Instance: TBase);
  public
    procedure Visit(Instance: IInterface);
  end;

implementation

uses
  SysUtils;

procedure TBaseVisitor.NotSupported(const Name: string);
begin
  // nach Bedarf ignorieren oder Fehler melden
end;

procedure TBaseVisitor.Visit(Instance: IInterface);
var
  visited: IVisited;
begin
  if Supports(Instance, IVisited, visited) then
    visited.Accept(Self)
  else
    // nach Bedarf ignorieren oder Fehler melden
end;

procedure TBaseVisitor.VisitBase(Instance: TBase);
begin
  // tue was mit Instance, was jedes TBase kann
end;

end.


Zitat:

Zitat von hansmaad (Beitrag 1035427)
TVisitor.VisitA(Instance: TA);
TVisitor.VisitB(Instance: TB);

würde ich wohl abstrakt machen und konkrete Besucher von TVisitor ableiten. Das wäre dann im Stil 'Template Methode'. Dieser Supports / Not Supported Kram wird von der Basisklasse behandelt, der konkrete Besucher kümmert sich nur noch um A und B.

Das hängt natürlich auch von deinem konkreten Fall ab. In meinen Anwendungen habe ich sowohl Visitors, die von einem Basis-Visitor abgeleitet sind, als auch solche, die von Grund auf neu implementiert werden.

Wenn irgendeine Struktur (Liste, Baum, ...) von TBase-Abkömmligen vorliegt, könnte man einen Visitor schreiben, der die Struktur iteriert und alle Instanzen zählt. Der bräcuhte dann nur IBaseVisitor implementieren.

Es gibt da keine allgemeingültige Vorgehensweise - wie so oft in unserer Branche...

Ich hatte mal vor, über das Thema "Visitor - eine praktikable Implementierung" einen Artikel zu schreiben, in dem die obige Implentierung erläutert und verschiedene Szenarien durchgespielt werden, aber es kommt halt immer etwas dazwischen.

Wär' ja vielleicht auch was für die Delphi-Tage???

hansmaad 15. Jul 2010 11:59

AW: Visitor Pattern
 
Zitat:

Zitat von Blup (Beitrag 1035431)
Im Normalfall Interface-Parameter besser als const deklarieren.
Sonst erfolgt unnötige Referenzzählung (wie auch bei string).

Was bedeuten in Delphi konstante Parameter? Was kann ich auf ihnen ausrufen? Von C++ kenne ich konstante Methoden, die auf konstanten Instanzen aufgerufen werden können. Sowas gibt es hier aber denke ich nicht.
Was bedeutet es einen const Visitor zu übergeben. Gilt dieses const nur für die Schnittstelle (eigentlich sinnlos da Schnittstellen gar keine Daten haben)? Oder weiß der Compiler, dass die Implementierung von Visit die Visitorinstanz nicht verändert? Was passiert, wenn Visit die Istanz verändert?

Uwe Raabe 15. Jul 2010 16:44

AW: Visitor Pattern
 
Zitat:

Zitat von Blup (Beitrag 1035431)
Im Normalfall Interface-Parameter besser als const deklarieren.
Sonst erfolgt unnötige Referenzzählung (wie auch bei string).
Delphi-Quellcode:
type
  IVisitor = interface
    ['{59A6BE7C-6BD7-4746-AA37-F42DCB6D8D01}']
    procedure Visit(const Instance: IInterface);
    procedure NotSupported(const Name: string);
  end;

  IVisited = interface
    ['{6C55ECC2-E1AB-43DD-96EC-755FF5C12400}']
    procedure Accept(const Visitor: IVisitor);
  end;

Ist nur meine persönliche Meinung, aber ich würde genau das nicht machen. Die "überflüssige" Referenzzählung ist ein Inkrement und ein Dekrement eines internen Zählers und fällt überhaupt nicht ins Gewicht. Die Deklaration als const allerding übergibt einen Pointer auf ein Interface und bei jedem Zugriff auf das Interface erfolgt eine zusätzliche Dereferenzierung des Pointers (und übrigens auch noch eine Referenzzählung). Also lediglich in dem Fall, wo das Interface innerhalb der Methode so gut wie nicht verwendet wird, mag die const Variante marginal schneller sein - in allen anderen Fällen ist sie weniger performant.

Namenloser 15. Jul 2010 17:16

AW: Visitor Pattern
 
Zitat:

Zitat von hansmaad (Beitrag 1035459)
Oder weiß der Compiler, dass die Implementierung von Visit die Visitorinstanz nicht verändert? Was passiert, wenn Visit die Istanz verändert?

Genau das ist
Delphi-Quellcode:
const
: Eine Schreibschutz-Markierung. Das erlaubt natürlich gewisse Compileroptimierungen, genauer gesagt kann einfach ein Pointer auf das Original übergeben werden, statt dass eine Kopie auf dem Stack angelegt werden muss.

Zu deiner 2. Frage: Das geht nicht, der Compiler schmeißt dann einen Fehler.

Im Grunde ist
Delphi-Quellcode:
const
das gleiche wie
Delphi-Quellcode:
var
, nur mit dem Unterschied, dass das Original garantiert nicht verändert wird.

himitsu 15. Jul 2010 17:34

AW: Visitor Pattern
 
CONST ist nicht immer genau das Gleiche wie VAR,
das siehst du, wenn du z.B.
Delphi-Quellcode:
const i: Integer
mit
Delphi-Quellcode:
i: integer
und
Delphi-Quellcode:
var i: Integer
vergleichst, denn dieses wird delphi meißt so weit optimieren, daß hier kein Pointer, sondern direkt der Wert mitgenommen wird, trots des CONST.
dennoch können, vorallem bei referenzzählenden Typen oder gar dem "kranken" WideString und Records, durch das CONST dennoch so einige kleine Stellen optimiert werden, da man hier explizit angibt, daß der Wert nicht verändert wird.

hansmaad 16. Jul 2010 10:11

AW: Visitor Pattern
 
Also jetzt brauch ich doch noch mal eine genauere Erklärung.
Ich habe es gerade getestet und ich kann auf einem const Argument alles aufrufen und es auch durch nicht konstante Methoden verändern. Was heißt hier const?

Delphi-Quellcode:
type
    Test = class
    private
        x : Integer;
    public
        constructor Create;
        procedure Konstant();
        procedure NichtKonstant();
    end;

{ Test }

constructor Test.Create;
begin
    x := 1;
end;

procedure Test.Konstant;
begin
    WriteLn(IntToStr(x));
end;

procedure Test.NichtKonstant;
begin
    x := x * 2;
end;

procedure Foo(const t : Test);
begin
    t.Konstant;
    t.NichtKonstant;
    t.Konstant;
end;


var
    Tester : Test;
begin
    Tester := Test.Create;

    Foo(Tester);

    Tester.Free;
    ReadLn;
end.
Code:
1
2

himitsu 16. Jul 2010 10:21

AW: Visitor Pattern
 
CONST bezieht sich immer nur auf die Variable/Parameter.

Es kann also der Parameterwert nicht verändert werden (Interface freigeben oder ein Anderes einfügen), aber den Inhalt von Objekten und Interfaces betrifft dieses nicht.

Delphi-Quellcode:
t.NichtKonstant;
ändert ja nur den Inhalt und nicht den Parameter, bzw. den Zeiger in dem Parameter, welcher auf das Objekt zeigt.

sowas geht nicht
Delphi-Quellcode:
procedure Foo(const t : Test);
begin
  t := nil;
  t := WasAnderes;
end;
Es gibt nur eine Stelle, wo CONST wirklich versagt ... und das ist bei Records, welche "neuerdings" mit Record-Methoden versehen werden können.
Eigentlich dürfte man bei Records, da sie nicht über einen Zeiger, sondern direkt verwalte werden, nicht den Inhalt verändern, welches aber dennoch leider über die RecordMethoden möglich ist. :cry:

hansmaad 16. Jul 2010 10:25

AW: Visitor Pattern
 
Ah ok, jetzt ists klar.
Gibt es auch ein const, das die Instanz konstant macht. So dass die Funktion das übergebene Objekt nicht verändert darf?

himitsu 16. Jul 2010 10:30

AW: Visitor Pattern
 
Leider nein.

Für sowas müßtest du selber den Schreibschutz direkt in das Objekt/Interface einbauen.

hansmaad 16. Jul 2010 10:38

AW: Visitor Pattern
 
Schade, aber trotzdem Danke:wink:

Gibt es ein empfehlenswertes Buch, dass solche Dinge wie in diesem Thread bespricht (Patterns in Delphi, gutes Design...)?
Die meisen Delphi Bücher die ich mir angesehen habe, sahen nach dem Schema "Klicken Sie Form erstellen um ein Form zu erstellen" aus. Gerne auch englisch.

Blup 16. Jul 2010 14:42

AW: Visitor Pattern
 
Nicht Delphi aber gut verständlich und unterhaltsam: Entwurfsmuster von Kopf bis Fuß
Die Beispiele kann man auch sehr leicht in Delphi umsetzen.

Sherlock 16. Jul 2010 14:48

AW: Visitor Pattern
 
1) gibt es sowieso fast keine aktuellen Delphibücher. IMHO spricht das FÜR Delphi ;)
und b) Ich denke da sollten Bücher, die andere Sprachen zur Erklärung verwenden auch möglich sein.

Sherlock

hansmaad 16. Jul 2010 14:53

AW: Visitor Pattern
 
Bücher wie GoF usw hab ich schon im Regal stehen. Ich dachte eher an etwas speziell auf Delphi bezogen. Vielleicht verschwenden Delphi Entwickler ja nicht so gern ihre Zeit mit Lesen :lol:

Sherlock 18. Aug 2010 13:33

AW: Visitor Pattern
 
Ha! Hier gibts das Visitor Pattern in Delphi :)

Sherlock

Uwe Raabe 18. Aug 2010 14:59

AW: Visitor Pattern
 
Zitat:

Zitat von Sherlock (Beitrag 1043060)
Ha! Hier gibts das Visitor Pattern in Delphi :)

Und für D2010-Besitzer sogar noch eine ganz schicke Lösung im vierten Teil der Serie :)

Stevie 18. Aug 2010 17:15

AW: Visitor Pattern
 
Zitat:

Zitat von Uwe Raabe (Beitrag 1043105)
Zitat:

Zitat von Sherlock (Beitrag 1043060)
Ha! Hier gibts das Visitor Pattern in Delphi :)

Und für D2010-Besitzer sogar noch eine ganz schicke Lösung im vierten Teil der Serie :)

Nicht nur für Delphi 2010 Besitzer :P

Uwe Raabe 18. Aug 2010 18:10

AW: Visitor Pattern
 
Zitat:

Zitat von Stevie (Beitrag 1043148)
Zitat:

Zitat von Uwe Raabe (Beitrag 1043105)
Zitat:

Zitat von Sherlock (Beitrag 1043060)
Ha! Hier gibts das Visitor Pattern in Delphi :)

Und für D2010-Besitzer sogar noch eine ganz schicke Lösung im vierten Teil der Serie :)

Nicht nur für Delphi 2010 Besitzer :P

Tatsächlich! Das hatte ich vollkommen übersehen. Ich hatte einfach nur mal ein bisschen mit RTTI rumgespielt...


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:37 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz