Delphi-PRAXiS
Seite 1 von 2  1 2   

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Callbacks: Interface und/oder Methode? (DEC 6.0) (https://www.delphipraxis.net/206451-callbacks-interface-und-oder-methode-dec-6-0-a.html)

himitsu 23. Dez 2020 02:33

Callbacks: Interface und/oder Methode? (DEC 6.0)
 
So, da Einige mit den Interfaces als Callback nicht klar kommen (es ihnen zu umständlich ist),
mal eine Frage, was man nun am Besten machen könnte.
  • alles so lassen, wie es ist (nur IDECProgress)
    nur mag es niemand -> https://www.delphipraxis.net/206384-...ml#post1479406
  • das mit dem Interface lassen, aber das dafür bissl verändern (nur IDECProgress2)
    • das Originale Interface hat den Nachteil, dass man den Anfang und vorallem das Ende nicht sicher erkennen kann
  • zusätlich einen Methoden-Callback implementieren (alles Mögliche reinmachen)
    • überall bei jedem Verwender die Überladung für jeden CallbackType -> https://github.com/geheimniswelten/D...p/IDECProgress
    • eine manuelle Überladung mit einem WrapperObjekt (die Verwender nehmen immer nur das Interface)
    • vielleicht kennt doch noch jemand eine Möglichkeit für einen impliziten Cast
  • das Interface durch eine Methode ersetzen (nur TDECProgress2), also weiterhin nur Einwas)
  • oder irgendwas Anderes ...
Es darf auch gern jemand zu den Bezeichnern (Methodennamen) seinen Senf zu eben, da ich das Gefühl hab, das geht bestimmt viel besser. (IDECProgress2)

----------

So ist es aktuell
Delphi-Quellcode:
type
  // das war Einigen zu schwer verständlich zu verstehen
  {IDECProgress = interface
    procedure Progress(const Min, Max, Pos: Int64);
  end;}

  // aber umbenannt und mit Kommentar ist es nicht wirklich besser geworden
  IDECProgress = interface
    /// <summary>...
    procedure OnProgress(const Min, Max, Pos: Int64);
  end;

  IDECHash = interface
    ...
    /// <summary>...
    procedure CalcStream(const Stream: TStream; Size: Int64; var HashResult: TBytes;
                         const OnProgress: IDECProgress = nil);
    ...
  end;


procedure TMyClass.OnProgress(const Min, Max, Pos: Int64);
begin
  ProgressBar.Min := Min; // 0;
  ProgressBar.Max := Max; // 100;
  ProgressBar.Position := Pos; // (Pos - Min) * 100 div (Max - Min);
end;
Dieses nun mit einer zusätzlichen EventMethode überladen
und weiter zusätzlich noch ein "optimaleres" Interface.
Es gibt es an sehr vielen Stellen nun die einbindenen Methoden somit unschön doppelt/dreifach,
wenn man das Alte aus Kompatibilitätsgründen beibehalten möchte, außerdem hat ein Interface auch seine Vorteile.
Delphi-Quellcode:
// siehe https://github.com/geheimniswelten/DelphiEncryptionCompendium/compare/development...geheimniswelten:develop/IDECProgress
// funktioniert, aber an vielen Stellen "unschön" mehrfache Implementation, für jeden Callback-Type

  {$IFDEF FPC}
    // ich weiß noch nicht ob/wie das mit den Anonymen im Lazarus/FPC geht
    TDECProgress = procedure(Sender: TObject; const Min, Max, Pos: Int64) of object;
  {$ELSE}
    TDECProgress = reference to procedure(Sender: TObject; const Min, Max, Pos: Int64);
  {$ENDIF}

  IDECProgress = interface
    procedure OnProgress(const Min, Max, Pos: Int64);
  end;

  IDECProgress2 = interface
    procedure OnProgressStart(const Min, Max: Int64);
    procedure OnProgress(const Pos: Int64 {; Percent: Single});
    procedure OnProgressStop(const Pos: Int64 {; Percent: Single});
    procedure OnProgressError(const Pos: Int64 {; Percent: Single}; const ErrorText: string);
  end;

  IDECHash = Interface
    ...
    /// <summary>...
    procedure CalcStream(const Stream: TStream; Size: Int64; var HashResult: TBytes;
                         const OnProgress: IDECProgress2 = nil); overload;
    procedure CalcStream(const Stream: TStream; Size: Int64; var HashResult: TBytes;
                         const OnProgress: IDECProgress); overload;
    procedure CalcStream(const Stream: TStream; Size: Int64; var HashResult: TBytes;
                         const OnProgress: TDECProgress); overload;
    ...
  end;


procedure TMyClass.OnProgressStart(const Min, Max: Int64);
begin
  ProgressBar.Min := Min;
  ProgressBar.Max := Max;
  ProgressBar.State := pbsNormal;
  ProgressBar.Position := 0;
  StatusLabel.Visible := False;
end;

procedure TMyClass.OnProgress(const Pos: Int64);
begin
  ProgressBar.Position := Pos;
end;

procedure TMyClass.OnProgressStop(const Pos: Int64);
begin
  ProgressBar.Position := Pos;
  ProgressBar.State := pbsPaused;
end;

procedure TMyClass.OnProgressError(const Pos: Int64; const ErrorText: string);
begin
  ProgressBar.Position := Pos;
  ProgressBar.State := pbsError;
  StatusLabel.Caption := ErrorText;
  StatusLabel.Visible := True;
end;
Vererbung würde das Ausmaß minimieren, aber eine Vererbung ist hier nicht "sinnvoll" möglich
und an vielen Stellen ist es weiterhin doppelt (Methode+Interface), auch wenn beide Interfaces nun im selben Parameter-Vorfahren übergeben werden könnten.
Delphi-Quellcode:
// ist keine Lösung

type
  IDECProgress = interface
    procedure OnProgress(const Min, Max, Pos: Int64);
  end;

  IDECProgress2 = interface(IDECProgress)
    {weiterhin ist das das alte OnProgress vorhanden
    procedure OnProgress(const Min, Max, Pos: Int64); }
    procedure OnProgressStart(const Min, Max: Int64);
    procedure OnProgress(const Pos: Int64 {; Percent: Single}); overload;
    procedure OnProgressStop(const Pos: Int64 {; Percent: Single});
    procedure OnProgressError(const Pos: Int64 {; Percent: Single}; const ErrorText: string);
  end;
Die Verwebung zu verschieben wird bestimmt auch niemand verstehen,
also mit einem IDECProgressBase oder gar direkt IInterface als Parameter.
Und ins letzte Interface passen die Methoden des Originals nicht rein, bzw. ich würde den doppelten OnProgress-Aufruf unschön finden.
Delphi-Quellcode:
// unverständliche mögliche Lösung

type
  IDECProgressBase = interface
    // ohne was drin
  end;

  IDECProgress = interface(IDECProgressBase)
    procedure OnProgress(const Min, Max, Pos: Int64);
  end;

  IDECProgress2 = interface(IDECProgressBase)
    {weiterhin ist das das alte OnProgress vorhanden
    procedure OnProgress(const Min, Max, Pos: Int64); }
    procedure OnProgressStart(const Min, Max: Int64);
    procedure OnProgress(const Pos: Int64 {; Percent: Single}); overload;
    procedure OnProgressStop(const Pos: Int64 {; Percent: Single});
    procedure OnProgressError(const Pos: Int64 {; Percent: Single}; const ErrorText: string);
  end;
Statt der vielen Überladungen könnte man auch nur das größte Interface (IDECProgress2) implementieren
und einen "manuellen" Wapper bauen, aber das versteht scheinbar auch niemand, bzw. es ist zu umständlich.
https://www.delphipraxis.net/206384-...ml#post1479406
Allerdings ist das von den "möglichen" Implementierung her eigentlich die schönste/schlankeste Variante,
auch wenn man bei Übergabe einer Callback-Methode, oder des kleinen Interfaces, einen manuellen Cast einfügen muß.
Delphi-Quellcode:
// funktioniert und kompakt (aber findet bzw. versteht vielleicht niemand)

type
  TDECProgressWrapper = class(TInterfacedObject, IDECProgress2)
  private
    ...
  public
    class function Create(Sender: TObject; Intf: IDECProgress): IDECProgress2; overload;
    class function Create(Sender: TObject; Proc: TDECProgress): IDECProgress2; overload;
  end;


Hash := DECHash.CalcStream(Stream, Size, TDECProgressWrapper.Create(OnProgressMethodOrIDECProgress));
Als Record-Helper mit Implicit-Class-Operator an dem Interface,
das funktioniert leider nicht. :cry:
Delphi-Quellcode:
// compiliert leider (noch) nicht

type
  // [dcc32 Fehler] E2474 Record-Typ erforderlich
  TDECProgressHelper = record helper for IDECProgress2
    class operator Implicit(Value: IDECProgress): IDECProgress2;
    class operator Implicit(Value: TDECProgress): IDECProgress2;
  end;

  // [dcc32 Fehler] E2021 Klassentyp erwartet
  TDECProgressHelper = class helper for IDesignerHook
    class operator Implicit(Value: IDECProgress): IDECProgress2;
    class operator Implicit(Value: TDECProgress): IDECProgress2;
  end;

  // aber per se kann man scheinbar doch Casts nachträglich anhängen (der Compiler meckert nicht)
  TIntegerHelper = record helper for Integer
    class operator Implicit(Value: Byte): Integer;
  end;
  TPointHelper = record helper for TPoint
    class operator Implicit(Value: TRect): TPoint;
  end;
Aber geil wäre es schon, wenn die Überladungen nur an einer Stelle liegen würden, wie auch beim Wrapper,
anstatt überall bei jeder Verwendung als overload einbauen zu müssen.

Umsetzung siehe https://github.com/geheimniswelten/D...p/IDECProgress


-----

Falls noch jemand andere Lösungen kennt ... bitte immer her damit.

Ansonsten fällt mir nur noch eine radikale "übersichtliche" Lösunge ein,
also das Interface komplett zu löschen und durch eine Event-Methode zu ersetzen.

dummzeuch 23. Dez 2020 08:35

AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
 
Vielleicht ist das mit den Interfaces nur deshalb ein Problem, weil viele über das "Ich Doppelklicke auf einen Button und schreibe den Code da hin"-Stadium nie hinauskommen. Ich befürchte, dass ca. 50% aller Delphi Entwickler sich noch nie mit Interfaces beschäftigt haben und verstehen, wozu sie gut sind und worauf man achten muss. Sie kommen halt bei der "normalen" Entwicklung eines GUI-Programms gar nicht vor.

Dazu kommt noch, dass die Implementation von Interfaces in Delphi unübersichtlich ist. Es ist nur schwer zu erkennen, welche Methoden einer Klasse ein Interface implementieren (und welches) und welche "ganz normale Methoden" sind. Da könnte die IDE deutlich nachhelfen.

Wenn es aber nur um einen Callback für den Fortschritt geht, finde ich ein Interface auch Overkill. Da wäre ein einfacher Event ausreichend. Das ändert sich, sobald es mehrere Callbacks gibt, dann ist ein Interface im Prinzip einfacher, allerdings muss man dann immer alle Methoden implementieren, auch die, die man vielleicht gar nicht braucht.

Vielleicht hilft es auch schon, Beispiele zur Verfügung zu stellen? Ich kenne DEC nicht, deshalb weiß ich nicht, wie gut die Doku ist.

Der schöne Günther 23. Dez 2020 09:02

AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
 
Zitat:

Zitat von dummzeuch (Beitrag 1479734)
Wenn es aber nur um einen Callback für den Fortschritt geht, finde ich ein Interface auch Overkill.

Dies.

generic 23. Dez 2020 11:30

AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
 
Moin, du hast dir gute Gedanken gemacht und alles sehr schön abgewogen. :thumb:

Auch wenn ich ein großer Fan von Interfaces bin, solltest du auf Callback-Funktionen setzen.
Die DEC wird in Konsolen-, VCL, FMX und Diensten eingesetzt.
Würdest du die Interfaces wählen, dann müsste zumindest für die Konsole überhaupt erstmal eine Klasse her, welche das Interface implementiert.
Du zwingst dann den Entwickler mehr zu machen als Notwendig wäre.

Daher würde ich es gut finden, wenn der Entwickler, den Callback als anonyme Funktion übergeben kann.
(oder halt auch ein Zeiger auf eine Funktion)


Für alle, welche gerade Bahnhof verstehen im CodingBott Youtube-Channel gibt es Videos zu dem Thema:

Zurück zum Aufrufer - Strategien zum zurückgeben von Daten
https://www.youtube.com/watch?v=cWaQkXX02gM

Zeiger auf Methoden und Funktionen in Delphi
https://www.youtube.com/watch?v=7PL_H2aBUOI

Der Weg zum Interface und deren Vorteile
https://www.youtube.com/watch?v=S5kX1N2G5hg

himitsu 23. Dez 2020 14:43

AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
 
Ja, dieses ein-prozedurige Interface ist eh "blöd".
Zitat:

das Originale Interface hat den Nachteil, dass man den Anfang und vorallem das Ende nicht sicher erkennen kann
Zitat:

Zitat von generic (Beitrag 1479745)
Daher würde ich es gut finden, wenn der Entwickler, den Callback als anonyme Funktion übergeben kann.
(oder halt auch ein Zeiger auf eine Funktion)

Jupp, wenn Callback-Methode, dann hatte ich eh an
Delphi-Quellcode:
reference to procedure
gedacht, damit der Entwickler da ganz frei alles übergeben kann.
Bezüglich dem FPC muß ich noch nachsehn ob/wie das dort geht.

* man könnte das einfache Interface ersetzen oder ein zweites "Besseres" danebenlegen (Abwärtskompatibilität)
* man kann einen einfachen Prozedurzeiger zusätzlich daneben hinzufügen oder das Interface komplett ersetzen (rauswerfen)

Demos:
der aktuelle Hauptthread zum Projekt
https://www.delphipraxis.net/206356-...erfuegbar.html
Code eigentlich hier (diese Demo steckt noch in der Entwicklung fest, drum nicht im Master)
https://github.com/MHumm/DelphiEncry...elopment/Demos
aber bei mir liegt da grade der aktuellere Code (hab grad erst die ProjekteOptionen aufgeräumt, und Dergleichen)
https://github.com/geheimniswelten/D...s/Progress_VCL
https://github.com/MHumm/DelphiEncry...endium/pull/11
und Seite 3 ganz oben, da hat jemand die Demo grade nochmal überarbeitet (hab ich mir noch nicht angesehn)
https://www.delphipraxis.net/206356-...ml#post1479751

[edit]
Ich denk mal so wäre es doch eine gute Lösung?
Delphi-Quellcode:
// alt
IDECProgress = interface
  procedure Progress(const Min, Max, Pos: Int64);
end;

// neu
TDECProgressState = (Start, Progress, Finish, Error);
TDECProgressParams = record
  Sender: TObject;
  Min, Max: Int64;
  Pos: Int64;
  Percent: Single;
  State: TDECProgressState;
  function ErrorMessage: string; // die Exception.Message aus dem umgebenden Try-Except-Block (oder als "function Error: Exception;", falls wer nicht selber auf System.ExceptObj zugreifen kann)
end;
TDECProgress = reference to procedure(const Progress: TDECProgressParams);
Bei Objekt-Methode und anonymer Methode bekommt man eigene Daten in den Callback.
Und bei einer Prozedur kann man sich über den Sender (die übergeordnete DEC-Klasse) als Vergleichswert zu einer Datenquelle durcharbeiten.

Oder sollte man hier vielleicht auch noch eine Data-Variable durchschleifen? (NativeInt, Pointer oder TObject)
Eigentlich nicht nötig, denn wer was braucht, der kann ja Methode statt Prozedur benutzen.

Nur die neue coole Interface-CallbackDemo muß dann wer umbauen. :stupid:

TurboMagic 23. Dez 2020 16:21

AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
 
Hallo,

soweit ich es derzeit abschätzen kann wäre ich auch für die Variante mit der Anonymen Methode.
Dann kann man entweder eine Methode oder eine normale Prozedur übergeben oder was direkt tun
(Anonyme Methode "in-place"). Verstehe ich das richtig?

Und auf die FPC Kompatibilität lege ich momentan ehrlich gesagt keinen übertriebenen Wert.
Wenn wir das dazu kompatibel hinbekommen, ok, aber das soll uns nicht ausbremsen.

Grüße
TurboMagic

generic 23. Dez 2020 16:41

AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
 
Wenn du eine Function draus machst, könnte ein FALSE sagen "weiter machen" und ein TRUE als "Abbruch" gewertet werden für die Callbackfunktion bzw. deren Rückgabe.
Also ist das Result die Antwort auf "Soll der Vorgang abgebrochen werden?"

Bei dem MIN-Wert bin ich mir auch nicht so sicher, ob der gebraucht wird. Übergibst du jemals was anderes als 0?

himitsu 23. Dez 2020 16:51

AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
 
(wenn ich es richtig gesehen/verstanden habe)
Pos ist "eigentlich" nicht die "absolute" Position der Progess, sondern die Position im Stream.
Und per se kann der Stream auch erst ab der Mitte für x Bytes lang ver-/entschlüsselt werden.

Aber man könnte sich auch darauf einigen, dass es ab jetzt nur die Position des aktuellen Ver-/Entschlüsselungsproesses ist, dann kann MIN auch weg.

Zitat:

Zitat von TurboMagic (Beitrag 1479784)
Verstehe ich das richtig?

Jupp.


Hab grade (seit gestern) das Delphi ein zweites Mal neu installiert. (erstes Mal im Schlaf von 10.4 zu 10.4.1 und dann nochmal iOS/OSX dazu ... brauch es zwar nicht, aber Delphi schmeißt sonst unnetterweise die Einträge aus den Projekten :wall:)
Und wollte mir dann mal wieder (nach Jahren) ein Lazarus installieren und es in das TestScript aufnehmen ... dann sieht man erstmal, ob es noch kompatibel ist (Änderungen dann auch kompatibel zu halten, ist eine andere Sache und könnte man später nachholen)

himitsu 24. Dez 2020 21:01

AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
 
Zitat:

Zitat von generic (Beitrag 1479786)
Bei dem MIN-Wert bin ich mir auch nicht so sicher, ob der gebraucht wird. Übergibst du jemals was anderes als 0?

Jo, MIN ist die StartPosition des Streams.
Aber ich denke, dass alle damit übereinstimmen, dass es mit 0 bis Anzahl-1 ganz OK wäre, also Min=0 (nicht vorhanden) und Pos=aktueller Verarbeitungsfortschritt.
https://github.com/geheimniswelten/D...progress-event

Den einzigen Vorteil, denn Min=StartPos statt Min=0 hätte, wäre wenn jemand in einem Stream "stückchenweise" mehrere Teile einzeln verschlüsselt/entschlüsselt/hasht,
aber da ist es für den Entwickler bei der Anzeige auch kein Problem, den Offset selbst einzurechnen, womit Min=0 dennoch vollkommen OK wäre.



Allerdings gibt es noch einen Bug, denn TDECHash.CalcStream und TDECFormattedCipher.DoEncodeDecodeStream verhalten sich unterschiedlich
und das hab ich noch nicht behoben. Erstmal nur die Behandlung für den Callback geändert und beim Rest nochmal warten, falls jemand etwas aus gutem Grund anders sieht.

TDECFormattedCipher.DoEncodeDecodeStream (DECCipherFormats) fängt immer mit Min=Stream.Position an
und bei DataSize<0 wird nur noch die Länge automatisch berechnet.

Während TDECHash.CalcStream (DECHashBase) bei DataSize<0 die Stream.Position auf 0 setzt.
Also bei DataSize<0 wird der Stream verschoben und mit Min=0 gearbeitet, während bei DataSize>=0 mit Min=Stream.Position begonnen wird. :wall:

Jetzt könnte man sich noch über die Definition von -1 streiten.
  • -1 = "verarbeiter ALLES", also immer von Position 0 aus
  • -1 = bis zum Ende, ab der aktuellen Position (ich bin hierfür, also dass DataSize nur die DataSize beeinflusst und nicht auch noch die Position)

TurboMagic 28. Dez 2020 16:09

AW: Callbacks: Interface und/oder Methode? (DEC 6.0)
 
DataSize sollte tatsächlich die Stream Position nicht beeinflussen!
Der Stream wird ja erzeugt übergeben und nicht von der DEC erzeugt.

Grüße
TurboMagic


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:30 Uhr.
Seite 1 von 2  1 2   

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