Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Alternative zu Typecast => IInterface? (https://www.delphipraxis.net/190893-alternative-zu-typecast-%3D-iinterface.html)

bytecook 17. Nov 2016 09:40

Alternative zu Typecast => IInterface?
 
Hallo Leute,

ich bin die letzten Wochen immer wieder hier im Forum auf Interfaces gestoßen, und frage mich, ob diese meinen Code sicherer/eleganter machen könnten...

Folgende Ausgangssituation:

Habe eine Basisklasse TSVFXCustomCmd. Dieser dient als Vorfahr verschiedener Klassen, die u.a unterschiedliche Routinen zur Verfügung stellen.

Die Klasse TSVFXVisualCmd deklariert beispielsweise eine Routine mit dem Namen Prozess(), die in den abgeleiteten Routinen überschrieben werden.

Alle diese Klassen von einer abgeleiteten Containerklasse vom TObjectList verwaltet (OwnsObject ist True). Keine Generics im Moment.

Klassenableitungen

TSVFXCustomCmd <= Basisklasse, verschiedene Properties
TSVFXVisualCmd <= abgeleitet von TSVFXCustomCmd, stellt verschiedene Basiskommandos zur Verfügung
TSVFXMoveTo <= abgeleitet von TSVFXVisualCmd, Pfadkommando
..
TSVFXQBezier <= abgeleitet von TSVFXVisualCmd, Pfadkommando
..
TSVFXCircle <= abgeleitet von TSVFXVisualCmd, Primitivekommando
..
TSVFXTransformCmd <= abgeleitet von TSVFXCustomCmd, stellt verschiedene Basiskommandos zur Verfügung
TSVFXScale <= abgeleitet von TSVFXTransformCmd
TSVFXRotate <= abgeleitet von TSVFXTransformCmd
..
TSVFXAnimateCmd <= abgeleitet von TSVFXCustomCmd, stellt verschiedene Basiskommandos zur Verfügung
..
TSVFXColorCmd <= abgeleitet von TSVFXCustomCmd, stellt verschiedene Basiskommandos zur Verfügung
..

Es kommen immer wieder neue Befehle hinzu, einige Routinen müssen jedesmal um Typecasts erweitert werden, das hätte ich mir gerne gespart.

Beim Abarbeiten der Objektliste werden objektspezifisch verschiedene Kommandos ausgeführt. Momentan vergleiche ich jedes Objekt mit einem Typecast gegen einen Objekttyp und starte dann die Routinen beispielsweise wie folgt:

Ausgangsszenario
Code:
 
 if (Self[i]) is TSVFXVisualCommand) then begin
   (** TSVFXMoveTo **)
   if Self[i] is TSVFXMoveTo then begin
     TSVFXMoveTo(Self[i]).Process (APath);
   end;
   ..
   ..
 end;
Wunschszenario
Code:
 
 if (Self[i]) is TSVFXVisualCommand) then
   TSVFXVisualCommand(Self[i])).Process (APath); <= abgeleiteter Befehl soll ausgeführt werden.
Diese Typecasts gegen KlassenTypen <TSVFXMoveTo(Self[i]).Process (APath);> hätte ich teilweise gerne vermieden. Bei einem Typecast gegen TSVFXVisualCommand wird verständlicherweise nicht die vom eigentlichen Objekt überschriebene Override Routine aufgerufen. Deklariere ich das Interface in der Klasse TSVFXVisualCommand, so muss ich das nach meinem derzeitigen Wissensstand auch dort implementieren, somit bringt dies nichts. Implementiere ich dieses Interface in den abgeleiteten Klassen, so lande ich wieder beim Typecast...

Höchstwahrscheinlich übersehe ich hier etwas wesentliches, bin generell auf dem Holzweg, oder Typecast ist und bleibt mein Ding...

Gruß,

Peter

Der schöne Günther 17. Nov 2016 10:00

AW: Alternative zu Typecast => IInterface?
 
Zitat:

Zitat von bytecook (Beitrag 1353956)
Bei einem Typecast gegen TSVFXVisualCommand wird verständlicherweise nicht die vom eigentlichen Objekt überschriebene Override Routine aufgerufen.

Also entweder bin ich noch nicht lange genug wach, oder...

Ich verstehe nicht weshalb die ganzen Typecasts sein müssen. Du hast doch mal eine Oberklasse, das ist doch schon mal ordentlich was wert. Du kannst auf Unterklassen lustig typecasten, aber in der Implementierung wird immer die "unterste" aufgerufen:
Delphi-Quellcode:
program Project11;

{$APPTYPE CONSOLE}
{$R *.res}

type
   TBird = class
      public procedure makeNoise(); virtual;
   end;

   TDuck = class(TBird)
      public procedure makeNoise(); override;
   end;

   TCliffracer = class(TBird)
        public procedure makeNoise(); override;
    end;

procedure TCliffracer.makeNoise();
begin
   WriteLn('Skreeeeee');
end;

procedure TDuck.makeNoise;
begin
   WriteLn('Quack!');
end;

procedure TBird.makeNoise();
begin
   WriteLn( '(generic bird sounds)' );
end;

var
   bird: TBird;
begin
   bird := TDuck.Create();

   bird.makeNoise(); // Quack!
   TBird(bird).makeNoise(); // Quack!
   TDuck(bird).makeNoise(); // Quack!

   if (bird is TCliffracer) then
      TCliffracer(bird).makeNoise()
   else
      WriteLn('It''s not a cliffracer');

   readln;
end.
Was möchtest du jetzt mit Interfaces erreichen? Ich glaube mir müsste man das jetzt noch mal in einfachen Worten (am besten mit vielen Vokalen) erklären...

DeddyH 17. Nov 2016 10:01

AW: Alternative zu Typecast => IInterface?
 
Wieso solltest Du bei Verwendung von Interfaces wieder beim Typecast landen? Du musst doch lediglich ermitteln, ob die aktuelle Instanz ein bestimmtes Interface implementiert und das dann benutzen. Dafür musst Du noch nicht einmal eine bestimmte Klassenhierarchie einhalten, da der Verwender nur die Interfaces sieht und nicht die Klasse.
Delphi-Quellcode:
procedure TIrgendwas.Work(const Intf: IBasicInterface);
var
  FirstIntf: IFirstIntf;
  SecondIntf: ISecondIntf;
begin
  Intf.DoBasicWork;
  if Supports(Intf, IFirstIntf, FirstIntf) then
    FirstIntf.DoSomeWork;
  if Supports(Intf, ISecondIntf, SecondIntf) then
    SecondIntf.DoSomeOtherWork;
end;

bytecook 17. Nov 2016 10:59

AW: Alternative zu Typecast => IInterface?
 
Zitat:

Zitat von DeddyH (Beitrag 1353960)
Wieso solltest Du bei Verwendung von Interfaces wieder beim Typecast landen? Du musst doch lediglich ermitteln, ob die aktuelle Instanz ein bestimmtes Interface implementiert und das dann benutzen. Dafür musst Du noch nicht einmal eine bestimmte Klassenhierarchie einhalten, da der Verwender nur die Interfaces sieht und nicht die Klasse.
Delphi-Quellcode:
procedure TIrgendwas.Work(const Intf: IBasicInterface);
var
  FirstIntf: IFirstIntf;
  SecondIntf: ISecondIntf;
begin
  Intf.DoBasicWork;
  if Supports(Intf, IFirstIntf, FirstIntf) then
    FirstIntf.DoSomeWork;
  if Supports(Intf, ISecondIntf, SecondIntf) then
    SecondIntf.DoSomeOtherWork;
end;

ha - if supports habe ich übersehen - das wars, danke!

bytecook 17. Nov 2016 11:15

AW: Alternative zu Typecast => IInterface?
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1353959)
Zitat:

Zitat von bytecook (Beitrag 1353956)
Bei einem Typecast gegen TSVFXVisualCommand wird verständlicherweise nicht die vom eigentlichen Objekt überschriebene Override Routine aufgerufen.

Also entweder bin ich noch nicht lange genug wach, oder...

Ich verstehe nicht weshalb die ganzen Typecasts sein müssen. Du hast doch mal eine Oberklasse, das ist doch schon mal ordentlich was wert. Du kannst auf Unterklassen lustig typecasten, aber in der Implementierung wird immer die "unterste" aufgerufen:
Delphi-Quellcode:
program Project11;

{$APPTYPE CONSOLE}
{$R *.res}

type
   TBird = class
      public procedure makeNoise(); virtual;
   end;

   TDuck = class(TBird)
      public procedure makeNoise(); override;
   end;

   TCliffracer = class(TBird)
        public procedure makeNoise(); override;
    end;

procedure TCliffracer.makeNoise();
begin
   WriteLn('Skreeeeee');
end;

procedure TDuck.makeNoise;
begin
   WriteLn('Quack!');
end;

procedure TBird.makeNoise();
begin
   WriteLn( '(generic bird sounds)' );
end;

var
   bird: TBird;
begin
   bird := TDuck.Create();

   bird.makeNoise(); // Quack!
   TBird(bird).makeNoise(); // Quack!
   TDuck(bird).makeNoise(); // Quack!

   if (bird is TCliffracer) then
      TCliffracer(bird).makeNoise()
   else
      WriteLn('It''s not a cliffracer');

   readln;
end.
Was möchtest du jetzt mit Interfaces erreichen? Ich glaube mir müsste man das jetzt noch mal in einfachen Worten (am besten mit vielen Vokalen) erklären...

Weil ich die Kommandos in einer Objektliste (TSVFXCommands = Class(TObjectList)) halte, die als Items Objekte vom Typ TSVFXCustomCmd hält.
Je nach Objekttyp gibt es in der abgeleiteten Basisklasse unterschiedliche Befehle, die ausgeführt werden sollen.

Alle Ableitungen von TSVFXVisualCmd haben beispielsweise die Routine Process, alle Ableitungen von TSVFXAnimateCmd haben die Routine Animate
zur Verfügung. Je nach Parentklasse soll der Aufruf unterschieden werden. Wenn ich typengecasted TSVFXVisualCmd().Process aufrufe, wird jedoch explizit die Routine dieser Klasse aufgerufen, und nicht vom Nachfolger als überschrieben definierte Routine, weil ich ja nicht beispielsweise gegen TSVFXMoveTo caste. (Basisroutine ist als Virtual deklariert, Nachfolger überschreibt mittels Override).

Mit IInterface sollte ich nun das Problem eleganter umgehen können...

stahli 17. Nov 2016 11:27

AW: Alternative zu Typecast => IInterface?
 
Ich denke auch, wie vom Günther angedeutet, dass Du Deine Methoden nicht korrekt mit virtual und override deklariert hast.
(Dann sollte der Compiler Warnungen ausgeben im Sinne "Methode X der Klasse Y verbirgt Methode X der Basisklasse A" oder ähnliches)

Interfaces sind nützlich, bedingen aber auch einige Änderungen und zusätzlichen Aufwand.

Den kannst Du Dir evtl. sparen, wenn Du die Methoden der Basisklasse und der Ableitungen korrekt implementierst.

Zeig sonst evtl. mal Deine Klassendeklaration...

Lemmy 17. Nov 2016 11:27

AW: Alternative zu Typecast => IInterface?
 
Zitat:

Zitat von bytecook (Beitrag 1353978)
Mit IInterface sollte ich nun das Problem eleganter umgehen können...

nur zur Vorsicht gefragt: wie? IMHO hast Du einen Fehler in der Vererbungshierarchie und um den zu lösen brauchst Du nicht zwingend Interfaces.

Darüber hinaus würde ich dir empfehlen das Command-Pattern anzuschauen - bzw. wenn dir das zu abstrakt ist die TAction-Implementierung in Delphi. Wenn ich das richtig verstanden habe, willst / brauchst Du genau so was...

bytecook 17. Nov 2016 12:39

AW: Alternative zu Typecast => IInterface?
 
Zitat:

Zitat von stahli (Beitrag 1353983)
Ich denke auch, wie vom Günther angedeutet, dass Du Deine Methoden nicht korrekt mit virtual und override deklariert hast.
(Dann sollte der Compiler Warnungen ausgeben im Sinne "Methode X der Klasse Y verbirgt Methode X der Basisklasse A" oder ähnliches)

Interfaces sind nützlich, bedingen aber auch einige Änderungen und zusätzlichen Aufwand.

Den kannst Du Dir evtl. sparen, wenn Du die Methoden der Basisklasse und der Ableitungen korrekt implementierst.

Zeig sonst evtl. mal Deine Klassendeklaration...

Oh mann, das Problem saß/sitzt wie zumeist vor dem Bildschirm. Ich habe, aus welchem Grunde auch immer, beim Kopieren / Aufteilen der Unterkomponenten das Virtual nicht durch Override ersetzt, und das Problem an einer ganz anderen Stelle vermutet... :wall: :oops: man sollte sich doch die Compilermeldungen genau durchsehen, heul...

Na zumindest blicke ich jetzt besser bei den Interfaces durch...

Der schöne Günther 17. Nov 2016 12:42

AW: Alternative zu Typecast => IInterface?
 
Aber Jünge!

Du müsstest doch eine Compiler-Warnung bekommen
Delphi-Quellcode:
type
   TBase = class
      public procedure stuff(); virtual;
   end;

   TSub = class(TBase)
      public procedure stuff(); virtual;
   end;
in Form von
Zitat:

[dcc32 Warnung] Project15.dpr(15): W1010 Methode 'stuff' verbirgt virtuelle Methode vom Basistyp 'TBase'

Oder?

bytecook 17. Nov 2016 12:54

AW: Alternative zu Typecast => IInterface?
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1354006)
Aber Jünge!

Du müsstest doch eine Compiler-Warnung bekommen
Delphi-Quellcode:
type
   TBase = class
      public procedure stuff(); virtual;
   end;

   TSub = class(TBase)
      public procedure stuff(); virtual;
   end;
in Form von
Zitat:

[dcc32 Warnung] Project15.dpr(15): W1010 Methode 'stuff' verbirgt virtuelle Methode vom Basistyp 'TBase'

Oder?

es compilierte ...

Wie gesagt hatte ich mir die Warnungen nicht angesehen, dort stands ja drinnen...

[dcc32 Warnung] SVFXCommands.pas(189): W1010 Methode 'Process' verbirgt virtuelle Methode vom Basistyp 'TSVFXVisualCmd'

Code:
  (******************************************************************************)
  (******************************************************************************)
  (** TSVFXVisualCmd                                                          **)
  (******************************************************************************)
  (******************************************************************************)
  TSVFXVisualCmd = Class (TSVFXCustomCmd)
  private
    FGroupIndex                                              : UInt32;
    FConstraints                                             : TSVFXSizeConstraints;
    FShift                                                   : TFloatPoint;
    FConstraintValue                                         : TFloatPoint;

  protected
    procedure DoConstraintsChanged (Sender : TObject); Virtual;
    function GetConstrainedSize  (Const Value: TFloatPoint) : TFloatPoint; Virtual;
    function GetPoint            () : TPointInfo; Virtual; Abstract;
    function GetPreviousPoint    () : TPointInfo; Virtual;
    function GetSize : TFloatPoint; Virtual;
    function Process (APath : TFlattenedPath) : Boolean; Virtual; Abstract;

    property GroupIndex : UInt32
             read FGroupIndex
             write FGroupIndex;

    property Constraints : TSVFXSizeConstraints
             read fConstraints
             write fConstraints;
  public
    Constructor Create (AOwner : TSVFXCommands; Const ADescription : String); Override;
    Destructor Destroy; Override;

    procedure UpdateShift (Const Value : TFloatPoint); Virtual; Abstract;
    procedure UpdateValues; Virtual; Abstract;

    (** Der Befehl UpdateValues aktualisiert die "Arbeitsvariablen_", die u.a zur Pfadbildung   **)
    (** verwendet werden.                                                                       **)
    function ValuesAsText () : String; Virtual;

    property ConstraintValue : TFloatPoint
             read fConstraintValue;

    property Size : TFloatPoint
             read GetSize;

  end;


  (******************************************************************************)
  (******************************************************************************)
  (** TSVFXMoveTo - TSVFXCommand                                              **)
  (** Bewegt den Stift an die Position Startpoint                             **)
  (******************************************************************************)
  (******************************************************************************)
  TSVFXMoveTo = Class (TSVFXVisualCmd)
  private
    fPoint                                                   : TFloatPoint;
    fPoint_                                                   : TFloatPoint;
    FDirections                                              : TSVFXDirections;
    procedure SetPoint (Const Value : TFloatPoint);
  protected
    procedure AssignTo (Dest : TPersistent); Override;
    function Read1000 (AStream : TStream) : Integer; Override;
    function GetPoint : TPointInfo; Override;
    function GetPreviousPoint : TPointInfo; Override;
  public
    Constructor Create(AOwner : TSVFXCommands; Const ADescription : String); Override;

    function LoadFromStream (AStream : TStream) : Integer; Override;
    function SaveToStream  (AStream : TStream) : Integer; Override;
    function ValuesAsText  () : String; Override;
    procedure UpdateShift   (Const Value : TFloatPoint); Override;
    procedure UpdateValues  (); Override;
    function Process       (APath : TFlattenedPath) : Boolean; virtual;

    property Point : TFloatPoint
             read fPoint
             write SetPoint;

    property Directions : TSVFXDirections
             read FDirections
             write FDirections;

    property Flag;
    property GroupIndex;
  end;


Alle Zeitangaben in WEZ +1. Es ist jetzt 14:56 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