Einzelnen Beitrag anzeigen

Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
43.142 Beiträge
 
Delphi 12 Athens
 
#1

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

  Alt 23. Dez 2020, 02:33
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.
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.
Garbage Collector ... Delphianer erzeugen keinen Müll, also brauchen sie auch keinen Müllsucher.
my Delphi wish list : BugReports/FeatureRequests

Geändert von himitsu (23. Dez 2020 um 02:44 Uhr)
  Mit Zitat antworten Zitat