Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden. (https://www.delphipraxis.net/183857-brauche-idee-um-immer-wiederkehrenden-quellcode-zu-vermeiden.html)

sh17 10. Feb 2015 06:13

Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
Hallo,

ich habe hier eine unbestimmte Anzahl von Funktionen mit folgendem Aufbau:

Code:
function TuDies(_Param1);
var
  success : Integer;
  err : ErrorStruct;
begin
  Result := false;
  success := -1;
  while success < 0 do
  try
    FDB.Mutex.Acquire;
    try
      FDB.TuDies(_Param1,err);
    finally
      FDB.Mutex.Release;
    end;
    success := FDB.CheckError(err);
    Result := success = 0;
  except
    on E:Exception do FDB.CheckException(E,success);
  end;
end;

function TuDas(_Param1,_Param1);
var
  success : Integer;
  err : ErrorStruct;
begin
  Result := false;
  success := -1;
  while success < 0 do
  try
    FDB.Mutex.Acquire;
    try
      FDB.TuDas(_Param1,_Param,err);
    finally
      FDB.Mutex.Release;
    end;
    success := FDB.CheckError(err);
    Result := success = 0;
  except
    on E:Exception do FDB.CheckException(E,success);
  end;
end;

...
Die Funktionen unterscheiden sich nur im Aufruf im try finally Block.

Was für eine Möglichkeit gäbe es, den restlichen Teil auszulagern? Könnte man die eigentliche Funktion irgendwie als Parameter übergeben? Die können ja vom Parameteraufbau vollkommen verschieden sein.

Für Ideen wäre ich dankbar.

CarlAshnikov 10. Feb 2015 06:25

AW: Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
Eine Möglichkeit ist das mit anonymen Methoden zu lösen:

Delphi-Quellcode:
TMyProc = reference to procedure(Err: ErrorCode);

procedure TuEtwas(AProc: TMyProc);
var
  success : Integer;
  err : ErrorStruct;
begin
  Result := false;
  success := -1;
  while success < 0 do
  try
    FDB.Mutex.Acquire;
    try
      AProc(err);
    finally
      FDB.Mutex.Release;
    end;
    success := FDB.CheckError(err);
    Result := success = 0;
  except
    on E:Exception do FDB.CheckException(E,success);
  end;
end;

procedure TuDies(_Param1);
begin
  TuEtwas(
    Procedure(Err: ErrorCode)
    begin
      FDB.TuDies(_Param1, Err)
    end   
  )
end;

procedure TuDas(_Param1,_Param2);
begin
  TuEtwas(
    Procedure(Err: ErrorCode)
    begin
      FDB.TuDies(_Param1, _Param2, Err)
    end   
  )
end;

Captnemo 10. Feb 2015 06:28

AW: Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
Wie wärs mit overload?
Dann hast du zwar immernoch 2 Funktionen, aber kannst diese unter einem Funktionsnamen zusammenfassen.

sh17 10. Feb 2015 06:47

AW: Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
Zitat:

Zitat von CarlAshnikov (Beitrag 1289337)
Eine Möglichkeit ist das mit anonymen Methoden zu lösen:

Das klappt, vielen Dank.

Jetzt kann ich sogar noch die Prüfmethoden und den Zugriff auf den Mutex zum DB-Objekt private machen.

War nur noch eine kleine Korrektur nötig, falls das jemand mal nutzen möchte.

Delphi-Quellcode:
TMyProc = reference to procedure(var Err: ErrorCode);

Sir Rufo 10. Feb 2015 07:13

AW: Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
Nein, bitte nicht mit diesem Exception Handling.

Wenn du an etwas Generellem interessiert bist, dann wäre hier etwas
Delphi-Quellcode:
type
  Closure = record
    class function Retry( const AProc: TProc; const MaxRetryCount: Integer = 0 ): TProc; overload; static;
    class function Retry<T>( const AProc: TProc<T>; const MaxRetryCount: Integer = 0 ): TProc<T>; overload; static;
    class function Retry<T1, T2>( const AProc: TProc<T1, T2>; const MaxRetryCount: Integer = 0 ): TProc<T1, T2>; overload; static;
    class function Retry<T1, T2, T3>( const AProc: TProc<T1, T2, T3>; const MaxRetryCount: Integer = 0 ): TProc<T1, T2, T3>; overload; static;
  end;

 { Closure }

class function Closure.Retry( const AProc: TProc; const MaxRetryCount: Integer ): TProc;
begin
  Result := procedure
    var
      LRetryCount: Integer;
    begin
      LRetryCount := 0;
      while True do
        try
          AProc( );
          Exit;
        except
          Inc( LRetryCount );
          if LRetryCount >= MaxRetryCount
          then
            raise;
        end;
    end;
end;

class function Closure.Retry<T1, T2, T3>( const AProc: TProc<T1, T2, T3>; const MaxRetryCount: Integer ): TProc<T1, T2, T3>;
begin
  Result := procedure( Arg1: T1; Arg2: T2; Arg3: T3 )
    begin
      Retry(
        procedure
        begin
          AProc( Arg1, Arg2, Arg3 );
        end, MaxRetryCount )( );
    end;
end;

class function Closure.Retry<T1, T2>( const AProc: TProc<T1, T2>; const MaxRetryCount: Integer ): TProc<T1, T2>;
begin
  Result := procedure( Arg1: T1; Arg2: T2 )
    begin
      Retry(
        procedure
        begin
          AProc( Arg1, Arg2 );
        end, MaxRetryCount )( );
    end;
end;

class function Closure.Retry<T>( const AProc: TProc<T>; const MaxRetryCount: Integer ): TProc<T>;
begin
  Result := procedure( Arg: T )
    begin
      Retry(
        procedure
        begin
          AProc( Arg );
        end, MaxRetryCount )( );
    end;
end;
Im Übrigen sollten die Aufrufe
Delphi-Quellcode:
FDB.TuDies
und
Delphi-Quellcode:
FDB.TuDas
sich selber um den Lock (wieso eigentlich Mutex, brauchst du das Session- bzw. System-Global? Sonst würde ein
Delphi-Quellcode:
TMonitor
reichen) kümmern, denn der scheint ja immanent wichtig zu sein, also gehört der in diese Methoden rein.
Delphi-Quellcode:
type
  TFoo = class
  public
    procedure Foo( AParam: Integer );
    procedure Bar( AParam1, AParam2: Integer );
  end;

  { TFoo }

procedure TFoo.Bar( AParam1, AParam2: Integer );
begin
  TMonitor.EnterAutoLeave( Self ); {Lock}

  Writeln( 'TFoo.Bar(', AParam1, ',', AParam2, ')' );
end;

procedure TFoo.Foo( AParam: Integer );
begin
  TMonitor.EnterAutoLeave( Self ); {Lock}

  Writeln( 'TFoo.Foo(', AParam, ')' );
end;
Dieses
Delphi-Quellcode:
TMonitor.EnterAutoLeave
kommt durch einen
Delphi-Quellcode:
class helper
von mir, der ein Interface zurückliefet und dafür für das automatische Verlassen sorgt. Dadurch wirkt der Code gleich viel entspannter.

Aufruf von der gesamten Hütte ist jetzt ein gemütlicher Spaziergang
Delphi-Quellcode:
procedure Test;
var
  LFoo: TFoo;
begin
  LFoo := TFoo.Create;
  try
    Closure.Retry<Integer>( LFoo.Foo, 10 )( 42 );
    Closure.Retry<Integer, Integer>( LFoo.Bar, 10 )( 08, 15 );
  finally
    LFoo.Free;
  end;
end;
Lustig gell?

UPDATE

Ok, das mit dem Mutex habe ich jetzt auch verstanden, damit garantierst du, dass nur einer global auf die Datenbank zugreift. Ansonsten schmeisst der Mutex eine Exception. Das ändert aber nichts an meinem Vorschlag, ausser, dass man den Closure noch erweitert um ein Predicate, dass den Exception-Typ bekommt und dort entschieden wird, ob da wirklich weitergemacht werden soll, denn eine
Delphi-Quellcode:
EAccessViolation
ist nichts, wo ich es nochmals versuchen müsste, da ich ja eigentlich auf nur auf den Mutex warte.

sh17 10. Feb 2015 07:24

AW: Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
:) Auf Deine Antwort war ich schon gespannt. Die werde ich mir heute bei nem Kaffee mal anschauen, da klemmt es im Kopf grad. Das mit dem EnterAutoLeave ist natürlich auch sehr elegant.

Sir Rufo 10. Feb 2015 08:54

AW: Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
Mit den Anonymen Methoden kann man schon sehr lustige Dinge anstellen:
Delphi-Quellcode:
type
  Closure = record
    class function Memoize<T, TResult>( AFunc: TFunc<T, TResult>; AEqualityComparer: IEqualityComparer<T> = nil ): TFunc<T, TResult>; static;
  end;

class function Closure.Memoize<T, TResult>( AFunc: TFunc<T, TResult>; AEqualityComparer: IEqualityComparer<T> ): TFunc<T, TResult>;
var
  LDict: AutoRef<TDictionary<T, TResult>>; { AutoRef kapselt eine Instanz in einem Interface und sorgt für die automatische Freigabe }
begin
  LDict := TDictionary<T, TResult>.Create( AEqualityComparer );
  Result := function( Arg: T ): TResult
    begin
      if not LDict.Reference.TryGetValue( Arg, Result )
      then
        begin
          Result := AFunc( Arg );
          LDict.Reference.Add( Arg, Result );
        end;
    end;
end;
So, wozu braucht man das?
Delphi-Quellcode:
function fibonacci( n: Int64 ): Int64;
begin
  if n < 2
  then
    Result := n
  else
    Result := fibonacci( n - 1 ) + fibonacci( n - 2 );
end;

function fibonacci_memoize( n: Int64 ): Int64;
var
  fibonacci: TFunc<Int64, Int64>;
begin
  fibonacci := Closure.Memoize<Int64, Int64>(
      function( n: Int64 ): Int64
    begin
      if n < 2
      then
        Result := n
      else
        Result := fibonacci( n - 1 ) + fibonacci( n - 2 );
    end );
  Result := fibonacci( n );
end;

procedure memoize_test;
var
  LFibN, LFibResult: Int64;
  LStopwatch: TStopwatch;
begin
  LFibN := 40;
  LStopwatch := TStopwatch.StartNew;
  LFibResult := fibonacci( LFibN ); // Standard Umsatzung
  LStopwatch.Stop;
  Writeln( Format( 'fibonacci(%d) = %d (%dms)', [LFibN, LFibResult, LStopwatch.ElapsedMilliseconds] ) );

  LStopwatch := TStopwatch.StartNew;
  LFibResult := fibonacci_memoize( LFibN ); // mit Closure.Memoize
  LStopwatch.Stop;
  Writeln( Format( 'fibonacci(%d) = %d (%dms)', [LFibN, LFibResult, LStopwatch.ElapsedMilliseconds] ) );
end;
Beide berechnen exakt die gleichen Werte, spannend sind jetzt die Laufzeiten
Code:
fibonacci(40) = 102334155 (1101ms)
fibonacci(40) = 102334155 (0ms)

fibonacci(100) = (dauert mir zu lange)
fibonacci(100) = 3736710778780434371 (0ms)

Sherlock 10. Feb 2015 09:26

AW: Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
:shock: Kannst Du auch den Geschwindigkeitsunterschied erklären?

Sherlock

Uwe Raabe 10. Feb 2015 09:39

AW: Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
Zitat:

Zitat von Sherlock (Beitrag 1289371)
:shock: Kannst Du auch den Geschwindigkeitsunterschied erklären?

Die originale Funktion ruft sich zweimal rekursiv auf um den aktuellen Wert zu ermitteln.

F(n) = F(n-1) + F(n-2)

Damit wird

F(n-1) = F(n-2) + F(n-3)
F(n-2) = F(n-3) + F(n-4)

Die Rekursion ruft also die Berechnung eines Wertes für jede Rekursionsstufe zweimal auf (Ausnahme: n<2). Z.B. wird F(n-2) sowohl bei der Berechnung von F(n) direkt als auch indirekt bei der Berechnung von F(n-1) berechnet. Damit hast du O(2^n) Aufrufe.

Die optimierte Funktion schaut halt nach, ob sie den Wert schon berechnet hat und spart sich dann den rekursiven Aufruf, was zu einer linearen Komplexität führt.

Wie bei vielen Beispielen gibt es zur Lösung dieses Problems natürlich auch einen wesentlich einfacher zu durchschauenden Lösungsweg, aber hier geht es ja eigentlich darum, die Verwendung Anonymer Methoden zu demonstrieren und nicht einen effizienten Algorithmus für Fibonacci-Zahlen zu entwickeln.

sh17 10. Feb 2015 09:46

AW: Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
Zitat:

Zitat von Sir Rufo (Beitrag 1289340)
Ok, das mit dem Mutex habe ich jetzt auch verstanden, damit garantierst du, dass nur einer global auf die Datenbank zugreift. Ansonsten schmeisst der Mutex eine Exception. Das ändert aber nichts an meinem Vorschlag, ausser, dass man den Closure noch erweitert um ein Predicate, dass den Exception-Typ bekommt und dort entschieden wird, ob da wirklich weitergemacht werden soll, denn eine
Delphi-Quellcode:
EAccessViolation
ist nichts, wo ich es nochmals versuchen müsste, da ich ja eigentlich auf nur auf den Mutex warte.

Die Exception hat nichts mit dem Mutex zu tun, sondern mit der Absicherung der TCP-IP-Verbindung oder sonstigen Problemen:
Der Mutex dient der gemeinsamen Nutzung der Verbindung zum Server.

Zitat:

Zitat von Sir Rufo
Im Übrigen sollten die Aufrufe FDB.TuDies und FDB.TuDas sich selber um den Lock (wieso eigentlich Mutex, brauchst du das Session- bzw. System-Global? Sonst würde ein TMonitor reichen) kümmern, denn der scheint ja immanent wichtig zu sein, also gehört der in diese Methoden rein.

Warum sollten die Aufrufe den Lock selbst steuern? Schlägt der Aufruf der Funktion aus irgend einem Grund fehl (Lock nicht erhalten, Serverprobleme, Datenverbindung nicht da, etc), wird dies nach dem Aufruf ausgewertet. Die Funktion soll nur versuchen das Zeug loszuwerden.


Was ich auch noch festgestellt habe, var Parameter in TuDies(var _UID : Integer) können nicht aus der anonymen Methode genutzt werden, daher habe ich Varianten einbauen müssen:

Code:
 
function ToDies(var _UID : Integer) : Boolean;
begin
  Result := FDB.Call(procedure(var Err: ErrorStruct; var _ID : Integer)
  begin
    FDB.ToDies(_ID,err);
  end,_UID);
end;
Jetzt muss ich geistig nur noch den Zusammenhang zu deinem Closure-Record hinbekommen.

Uwe Raabe 10. Feb 2015 09:59

AW: Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
Zitat:

Zitat von sh17 (Beitrag 1289380)
Warum sollten die Aufrufe den Lock selbst steuern?

Es gibt da zwei Herangehensweisen und beide haben ihre Vor- und Nachteile. Die eine geht davon aus, daß der Funktionsaufruf alles Nötige kapselt und die Threadsicherheit herstellt, während die andere sich lediglich um die Funktionalität kümmert und bestimmte Anforderungen an die Umgebung stellt. In gewissem Sinne ist die zweite Art das, was du implementierst: Die einzelnen Funktionen werden mit einem Wrapper versehen, der sich um Locks und Error-Handling kümmert. Da ist erstmal nichts falsch dran.

Zitat:

Zitat von sh17 (Beitrag 1289380)
Was ich auch noch festgestellt habe, var Parameter in TuDies(var _UID : Integer) können nicht aus der anonymen Methode genutzt werden,

Das ist eine Einschränkung bei Anonymen Methoden. In diesem Fall kann der Compiler nicht wissen, wo die Variable wirklich liegt, und kann sie somit nicht einfach in die Closure verlagern.

Der schöne Günther 10. Feb 2015 11:06

AW: Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
Aber wenn man die Variable lokal einfach nochmal kopiert kommt eine Closure da doch problemlos dran, oder?

sh17 10. Feb 2015 11:11

AW: Brauche Idee, um immer wiederkehrenden Quellcode zu vermeiden.
 
Zitat:

Zitat von Der schöne Günther (Beitrag 1289398)
Aber wenn man die Variable lokal einfach nochmal kopiert kommt eine Closure da doch problemlos dran, oder?

Ja, das ginge, allerdings müsste ich da wieder extra eine Prüfung einbauen, ob die Variable rückzu gesetzt wurde, weil das nicht immer passiert.

Außerdem spinnt der Debugger bei der lokalen Variable, die erscheint dann nicht mehr zur Auswertung.


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