AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

[Beispiel] Wie funktionieren Interfaces?

Ein Thema von Sir Rufo · begonnen am 3. Feb 2016 · letzter Beitrag vom 5. Feb 2016
Antwort Antwort
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#1

[Beispiel] Wie funktionieren Interfaces?

  Alt 3. Feb 2016, 16:40
Im Anhang findet man unterschiedliche Arten die Interfaces
Delphi-Quellcode:
unit PlayWithInterfaces.Intf;

interface

type
  IFoo = interface
    [ '{39D32762-2113-4315-A06E-909B2F5D0832}' ]
    function GetFooInfo( ): string;
  end;

  IBar = interface
    [ '{F9F9977D-A03D-49A4-B8A9-ED3566C8D8C9}' ]
    function GetBarInfo( ): string;
  end;

  IFooBar = interface
    [ '{6ABC365F-94D5-461E-BB2C-406837E8287D}' ]
    function GetBar: IBar;
    property Bar: IBar read GetBar;
    function GetFoo: IFoo;
    property Foo: IFoo read GetFoo;
  end;

implementation

end.
zu implementieren.

Mit dem Konsolen-Programm
Delphi-Quellcode:
program PlayWithInterfaces;

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

uses
  System.StrUtils,
  System.SysUtils,
  PlayWithInterfaces.Intf in 'PlayWithInterfaces.Intf.pas',
  PlayWithInterfaces.Impl.Basic in 'PlayWithInterfaces.Impl.Basic.pas',
  PlayWithInterfaces.Impl.Split in 'PlayWithInterfaces.Impl.Split.pas',
  PlayWithInterfaces.Impl.Aggregated in 'PlayWithInterfaces.Impl.Aggregated.pas',
  PlayWithInterfaces.Impl.Contained in 'PlayWithInterfaces.Impl.Contained.pas',
  PlayWithInterfaces.Impl.Routed in 'PlayWithInterfaces.Impl.Routed.pas',
  PlayWithInterfaces.Impl.Attached in 'PlayWithInterfaces.Impl.Attached.pas';

procedure WorkWithBar( Bar: IBar; const Info: string );
begin
  Writeln( 'Bar - ', Info );
  Writeln( 'Bar.GetBarInfo = ', Bar.GetBarInfo );

  Writeln( 'Bar ', IfThen( Supports( Bar, IFoo ), '', 'NOT ' ), 'supports IFoo' );
  Writeln( 'Bar ', IfThen( Supports( Bar, IFooBar ), '', 'NOT ' ), 'supports IFooBar' );
  Writeln;
end;

procedure WorkWithFoo( Foo: IFoo; const Info: string );
begin
  Writeln( 'Foo - ', Info );
  Writeln( 'Foo.GetFooInfo = ', Foo.GetFooInfo );

  Writeln( 'Foo ', IfThen( Supports( Foo, IBar ), '', 'NOT ' ), 'supports IBar' );
  Writeln( 'Foo ', IfThen( Supports( Foo, IFooBar ), '', 'NOT ' ), 'supports IFooBar' );
  Writeln;
end;

procedure WorkWithFooBar( FooBar: IFooBar; const Info: string );
begin
  Writeln( 'FooBar - ', Info );
  Writeln;

  WorkWithBar( FooBar.Bar, 'FooBar.Bar' );
  WorkWithFoo( FooBar.Foo, 'FooBar.Foo' );

  Writeln( 'FooBar ', IfThen( Supports( FooBar, IBar ), '', 'NOT ' ), 'supports IBar' );
  Writeln( 'FooBar ', IfThen( Supports( FooBar, IFoo ), '', 'NOT ' ), 'supports IFoo' );
  Writeln;
end;

procedure Main;
var
  lAttached: TAttachedFooBar;
begin
  WorkWithFooBar( TBasicFooBar.Create, TBasicFooBar.ClassName );
  WorkWithFooBar( TSplitFooBar.Create, TSplitFooBar.ClassName );
  WorkWithFooBar( TAggregatedFooBar.Create, TAggregatedFooBar.ClassName );
  WorkWithFooBar( TContainedFooBar.Create, TContainedFooBar.ClassName );
  WorkWithFooBar( TRoutedFooBar.Create, TRoutedFooBar.ClassName );

  lAttached := TAttachedFooBar.Create;
  try
    WorkWithFooBar( lAttached, lAttached.ClassName );
  finally
    lAttached.Free;
  end;
end;

begin
  ReportMemoryLeaksOnShutdown := True;
  try
    Main;
  except
    on E: Exception do
      Writeln( E.ClassName, ': ', E.Message );
  end;
  ReadLn;

end.
kann man dann sehen, welche Unterschiede in den einzelnen Implementierungen bestehen und welche Klasse konkret die Information liefert.

IFoo.GetFooInfo und IBar.GetBarInfo liefern den Klassen-Namen und den Methoden-Namen zurück:
Code:
FooBar - TBasicFooBar

Bar - FooBar.Bar
Bar.GetBarInfo = TBasicFooBar.GetBarInfo
Bar supports IFoo
Bar supports IFooBar

Foo - FooBar.Foo
Foo.GetFooInfo = TBasicFooBar.GetFooInfo
Foo supports IBar
Foo supports IFooBar

FooBar supports IBar
FooBar supports IFoo

FooBar - TSplitFooBar

Bar - FooBar.Bar
Bar.GetBarInfo = TSplitBar.GetBarInfo
Bar NOT supports IFoo
Bar NOT supports IFooBar

Foo - FooBar.Foo
Foo.GetFooInfo = TSplitFoo.GetFooInfo
Foo NOT supports IBar
Foo NOT supports IFooBar

FooBar NOT supports IBar
FooBar NOT supports IFoo

FooBar - TAggregatedFooBar

Bar - FooBar.Bar
Bar.GetBarInfo = TAggregatedBar.GetFooInfo
Bar NOT supports IFoo
Bar supports IFooBar

Foo - FooBar.Foo
Foo.GetFooInfo = TAggregatedFoo.GetFooInfo
Foo NOT supports IBar
Foo supports IFooBar

FooBar NOT supports IBar
FooBar NOT supports IFoo

FooBar - TContainedFooBar

Bar - FooBar.Bar
Bar.GetBarInfo = TContainedBar.GetFooInfo
Bar NOT supports IFoo
Bar NOT supports IFooBar

Foo - FooBar.Foo
Foo.GetFooInfo = TContainedFoo.GetFooInfo
Foo NOT supports IBar
Foo NOT supports IFooBar

FooBar NOT supports IBar
FooBar NOT supports IFoo

FooBar - TRoutedFooBar

Bar - FooBar.Bar
Bar.GetBarInfo = TRoutedBar.GetFooInfo
Bar supports IFoo
Bar supports IFooBar

Foo - FooBar.Foo
Foo.GetFooInfo = TRoutedFoo.GetFooInfo
Foo supports IBar
Foo supports IFooBar

FooBar supports IBar
FooBar supports IFoo

FooBar - TAttachedFooBar

Bar - FooBar.Bar
Bar.GetBarInfo = TAttachedFooBarImplementer.GetBarInfo
Bar supports IFoo
Bar supports IFooBar

Foo - FooBar.Foo
Foo.GetFooInfo = TAttachedFooBarImplementer.GetFooInfo
Foo supports IBar
Foo supports IFooBar

FooBar supports IBar
FooBar supports IFoo
P.S. Von den ganz schrägen Implementierungen habe ich jetzt mal Abstand genommen.
Angehängte Dateien
Dateityp: zip PlayWithInterfaces.zip (4,5 KB, 51x aufgerufen)
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  Mit Zitat antworten Zitat
Benmik

Registriert seit: 11. Apr 2009
560 Beiträge
 
Delphi 12 Athens
 
#2

AW: [Beispiel] Wie funktionieren Interfaces?

  Alt 4. Feb 2016, 12:03
Tolle Sache, wirklich verdienstvoll, meinen Dank hast du.
  Mit Zitat antworten Zitat
Benutzerbild von stahli
stahli

Registriert seit: 26. Nov 2003
Ort: Halle/Saale
4.343 Beiträge
 
Delphi 11 Alexandria
 
#3

AW: [Beispiel] Wie funktionieren Interfaces?

  Alt 4. Feb 2016, 12:14
P.S. Von den ganz schrägen Implementierungen habe ich jetzt mal Abstand genommen.
Von mir auch danke, wobei ich über den Nachsatz dankbar bin, da mir die einfachen schrägen Sachen schon reichen.
Stahli
http://www.StahliSoft.de
---
"Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004)
  Mit Zitat antworten Zitat
Rollo62

Registriert seit: 15. Mär 2007
4.116 Beiträge
 
Delphi 12 Athens
 
#4

AW: [Beispiel] Wie funktionieren Interfaces?

  Alt 4. Feb 2016, 12:50
Super dankesehr.

Das sollte sich jeder mal übers Bett hängen.
In dieser sauberen Form finde ich es mehr als super, statt 30 Seiten drüber zu referieren.

!! Lasst den Code selber sprechen

Rollo
  Mit Zitat antworten Zitat
Benutzerbild von Sir Rufo
Sir Rufo

Registriert seit: 5. Jan 2005
Ort: Stadthagen
9.454 Beiträge
 
Delphi 10 Seattle Enterprise
 
#5

AW: [Beispiel] Wie funktionieren Interfaces?

  Alt 4. Feb 2016, 16:14
Wenn es gefällt, dann habe ich hier noch ein Beispiel zum Einsatz von Interfaces:

Wir sollen in einer Anwendung echte Zahlungen verarbeiten (ec-Terminal, Paypal, whatever).

Da wir bislang noch nicht wissen, wie das mit dieser Zahlung konkret abgewickelt wird und vor allem mit wem, wir aber die Oberfläche schon bauen wollen (und auch können) fangen wir erst einmal mit einem Interface an:
  • Es wird der Betrag übergeben
  • Nach Abschluss des Zahlvorgangs wird ein Callback(*) mit einem Response-Objekt aufgerufen. Dort befindet sich dann eine ID des Zahlungsdienstleisters und der bestätigte Betrag.
(*) Ein Callback, weil wir ja nicht wollen, dass uns die GUI einfriert, wenn die Zahlung überprüft wird

Warum überhaupt ein Interface und keine abstrakte Klasse?

Weil ich über die Implementierung und das Lifetime-Management noch gar nichts sagen kann. Hinter einem Interface kann man alles verstecken, so wie es in dem konkreten Fall nachher benötigt wird.

Also Interface:
Delphi-Quellcode:
unit Services;

interface

uses
  System.SysUtils;

type
  IGetMoney = interface
    procedure GetMoney( const Amount: Currency; Callback: TProc<TObject> );
  end;

type
  TGetMoneyResponse = class
  private
    FId : string;
    FAmount: Currency;
  public
    constructor Create( const Id: string; const Amount: Currency );
    property Id: string read FId;
    property Amount: Currency read FAmount;
  end;

type
  ServiceLocator = class sealed
  private
    class var FMoneyService: TFunc<IGetMoney>;
  public
    class property MoneyService: TFunc<IGetMoney> read FMoneyService write FMoneyService;
  end;

implementation

{ TGetMoneyResponse }

constructor TGetMoneyResponse.Create( const Id: string; const Amount: Currency );
begin
  inherited Create;
  FId := Id;
  FAmount := Amount;
end;

end.
Den ServiceLocator verwende ich hier, um einen Zugangspunkt zu meinen Services zu haben. Beim Start der Anwendung wird der einmal konfiguriert und dann einfach immer abgefragt.

Das war ja schon mal einfach, da können wir uns gleich an die Oberfläche machen:
Delphi-Quellcode:
unit Forms.MainForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TMainForm = class( TForm )
    AmountEdit: TEdit;
    RequestButton: TButton;
    Label1: TLabel;
    InfoMemo: TMemo;
    procedure RequestButtonClick( Sender: TObject );
    procedure AmountEditChange( Sender: TObject );
  private
    procedure MoneyServiceCallback( AObject: TObject );
    procedure GetMoney( const Amount: Currency );
    procedure WaitForMoneyService( const Amount: Currency );
    procedure FinishedMoneyService( );
  public
    { Public-Deklarationen }
  end;

var
  MainForm: TMainForm;

implementation

uses
  Services;

{$R *.dfm}

procedure TMainForm.RequestButtonClick( Sender: TObject );
begin
  GetMoney( StrToCurr( AmountEdit.Text ) );
end;

procedure TMainForm.AmountEditChange( Sender: TObject );
begin
  InfoMemo.Clear;
  InfoMemo.Color := clWindow;
end;

procedure TMainForm.FinishedMoneyService;
begin
  RequestButton.Enabled := True;
  AmountEdit.Enabled := True;
  AmountEdit.SetFocus;
end;

procedure TMainForm.GetMoney( const Amount: Currency );
begin
  if Amount <= 0
  then
    raise EArgumentOutOfRangeException.Create( 'Amount' );

  WaitForMoneyService( Amount );
  try

    // Wir fragen den ServiceLocator
    
    ServiceLocator.MoneyService( ).GetMoney( Amount, MoneyServiceCallback );

  except
    MoneyServiceCallback( AcquireExceptionObject( ) );
  end;
end;

procedure TMainForm.MoneyServiceCallback( AObject: TObject );
begin
  try

    if AObject is TGetMoneyResponse
    then { Zahlung ist akzeptiert }
      begin
        InfoMemo.Color := clLime;
        InfoMemo.Font.Color := clBlack;
        InfoMemo.Lines.Add( string.Format( 'Accepted %m with Id %s', [ TGetMoneyResponse( AObject ).Amount, TGetMoneyResponse( AObject ).Id ] ) );
      end
    else { irgendwas ist faul }
      begin
        InfoMemo.Color := clRed;
        InfoMemo.Font.Color := clWhite;
        if AObject is Exception
        then { eine Exception }
          InfoMemo.Lines.Add( Exception( AObject ).Message )
        else if Assigned( AObject )
        then { irgendwas unerwartetes }
          begin
            InfoMemo.Lines.Add( 'Unknown Response' );
            InfoMemo.Lines.Add( AObject.ToString );
          end
        else { gar nichts }
          InfoMemo.Lines.Add( 'Empty Response' );
      end;

  finally
    AObject.Free;
    FinishedMoneyService( );
  end;
end;

procedure TMainForm.WaitForMoneyService( const Amount: Currency );
begin
  AmountEdit.Enabled := False;
  RequestButton.Enabled := False;
  InfoMemo.Clear;
  InfoMemo.Color := clYellow;
  InfoMemo.Font.Color := clBlack;
  InfoMemo.Lines.Add( string.Format( 'Waiting for %m ...', [ Amount ] ) );
end;

end.
Ist im Prinzip relativ unspektakulär. Der Betrag wird aus dem Edit-Feld gelesen, der Service vom ServiceLocator geholt und wir rufen die Service-Methode mit dem Betrag und dem Callback auf.

Wenn der Callback aufgerufen wird, dann haben wir eine gültige Zahlung oder irgendetwas Komisches.

Protokolliert wird das in dem InfoMemo und die Hintergrundfarbe des Memos zeigt den Abfrage-Status an:
  • weiß: noch keine Abfrage gestartet
  • gelb: Abfrage läuft
  • grün: Zahlung erfolgreich
  • rot: Zahlung fehlgeschlagen
Soweit so gut. Jetzt wäre ein kleiner Mini-Fake-Service nett, damit wir sehen können, ob die Oberfläche auch so reagiert, wie wir uns das denken.
Delphi-Quellcode:
unit Services.Impl.Simple;

interface

uses
  System.Classes,
  System.SysUtils,
  Services;

type
  TSimpleService = class( TInterfacedObject, IGetMoney )
  public
    procedure GetMoney( const Amount: Currency; Callback: TProc<TObject> );
  end;

implementation

{ TSimpleService }

procedure TSimpleService.GetMoney( const Amount: Currency; Callback: TProc<TObject> );
begin
  TThread.CreateAnonymousThread(
    procedure
    begin
      Sleep( 2000 );

      TThread.Synchronize( nil,
        procedure
        begin
          Callback( TGetMoneyResponse.Create( TGUID.NewGuid.ToString( ), Amount ) );
        end );
    end ).Start;
end;

end.
Einfach einen Thread starten, 2 Sekunden warten und dann synchronisiert den Callback mit der Meldung aufrufen.

Und wie kriegen wir die jetzt zusammen? Mit einer Konfiguration:
Delphi-Quellcode:
unit Configuration;

interface

procedure SimpleConfiguration;

implementation

uses
  Services,
  Services.Impl.Simple;

procedure SimpleConfiguration;
begin
  ServiceLocator.MoneyService :=
    function: IGetMoney
    begin
      Result := TSimpleService.Create;
    end;
end;

end.
Und in der DPR Datei rufen wir einfach diese procedure auf
Delphi-Quellcode:
program MoneyThroughWire;

uses
  Vcl.Forms,
  Forms.MainForm in 'Forms.MainForm.pas{MainForm},
  Services in 'Services.pas',
  Services.Impl.Simple in 'Services.Impl.Simple.pas',
  Configuration in 'Configuration.pas';

{$R *.res}

begin
  Configuration.SimpleConfiguration;

  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
end.
Das ist ja schon mal ganz nett ... die Oberfläche kann man jetzt testen, ob die bei einem Erfolg alles richtig anzeigt.

Schön wäre ja, wenn man auch die anderen Fälle testen könnte ... dann bauen wir uns doch dafür mal einen Service, mit dem wir das bequem erledigen können:

Wir brauchen ein Formular
Delphi-Quellcode:
unit Forms.GetMoneySimpleDialogForm;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;

type
  TGetMoneySimpleDialogForm = class( TForm )
    Label1: TLabel;
    Button1: TButton;
    Button2: TButton;
    RadioGroup1: TRadioGroup;
    procedure FormShow( Sender: TObject );
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  GetMoneySimpleDialogForm: TGetMoneySimpleDialogForm;

implementation

{$R *.dfm}

procedure TGetMoneySimpleDialogForm.FormShow( Sender: TObject );
begin
  Left := Application.MainForm.Left + Application.MainForm.Width + 10;
  Top := Application.MainForm.Top;
end;

end.
und einen Service, der mit diesem Formular arbeitet:
Delphi-Quellcode:
unit Services.Impl.SimpleDialog;

interface

uses
  System.SysUtils,
  Services;

type
  TSimpleDialogService = class( TInterfacedObject
    {} , IGetMoney )
  private
    procedure GetMoney( const Amount: Currency; Callback: TProc<TObject> );
  end;

implementation

uses
  System.Classes, Forms.GetMoneySimpleDialogForm, System.UITypes;

{ TSimpleDialogService }

procedure TSimpleDialogService.GetMoney( const Amount: Currency; Callback: TProc<TObject> );
begin
  TThread.CreateAnonymousThread(
    procedure
    begin

      TThread.Synchronize( nil,
        procedure
        var
          lDialog: TGetMoneySimpleDialogForm;
          lDialogResult: Integer;
        begin

          try
            if Amount <= 0
            then
              begin
                raise EArgumentOutOfRangeException.Create( 'Amount out of Range' );
              end;

            lDialog := TGetMoneySimpleDialogForm.Create( nil );
            try
              lDialog.Label1.Caption := string.Format( '%m', [ Amount ] );

              lDialog.RadioGroup1.Items.Add( 'Not Accepted' );
              lDialog.RadioGroup1.Items.Add( 'Not Valid' );
              lDialog.RadioGroup1.Items.Add( 'Something completely different' );
              lDialog.RadioGroup1.Items.Add( 'nil' );

              repeat
                lDialogResult := lDialog.ShowModal;
              until ( lDialogResult = mrYes ) or ( ( lDialogResult = mrNo ) and ( lDialog.RadioGroup1.ItemIndex >= 0 ) );

              if lDialogResult = mrNo
              then
                case lDialog.RadioGroup1.ItemIndex of
                  0:
                    raise Exception.Create( 'Not Accepted' );
                  1:
                    raise Exception.Create( 'Not Valid' );
                  2:
                    Callback( TObject.Create );
                  3:
                    Callback( nil );
                else
                  raise ENotImplemented.CreateFmt( 'Index %d not implemented', [ lDialog.RadioGroup1.ItemIndex ] );
                end
              else
                Callback( TGetMoneyResponse.Create( TGUID.NewGuid.ToString, Amount ) );

            finally
              lDialog.Free;
            end;

          except
            Callback( AcquireExceptionObject( ) );
          end;
        end );

    end ).Start;
end;

end.
Jetzt noch die Konfiguration anpassen
Delphi-Quellcode:
unit Configuration;

interface

procedure SimpleConfiguration;
procedure SimpleDialogConfiguration;

implementation

uses
  Services,
  Services.Impl.Simple,
  Services.Impl.SimpleDialog;

procedure SimpleConfiguration;
begin
  ServiceLocator.MoneyService :=
    function: IGetMoney
    begin
      Result := TSimpleService.Create;
    end;
end;

procedure SimpleDialogConfiguration;
begin
  ServiceLocator.MoneyService :=
    function: IGetMoney
    begin
      Result := TSimpleDialogService.Create;
    end;
end;

end.
Und in der DPR natürlich auch die richtige procedure aufrufen.
Delphi-Quellcode:
begin
  // Configuration.SimpleConfiguration;
  Configuration.SimpleDialogConfiguration;

  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
end.
Schon können wir jeden Fall in der Anwendung testen, obwohl wir noch nicht ein Stück Code zum konkreten ZahlungsService haben.

Ist der konkrete Zahlungs-Service dann fertig, dann wird dieser über die Konfiguration verdrahtet und verwendet.

Die Konfigurationen kann man natürlich auch schön über einen Compiler-Switch auswählen, der z.B. durch eine Build-Konfiguration gesetzt wird. Dann bekommt man z.B. eine Version, wo man nur die Oberfläche testen kann, ohne gleich mehrere tausend Euro über PayPal bewegen zu müssen.

Im Anhang Source und EXE (dort gibt es noch ein NotifyService, der sich einfach mal dazwischen klemmt und während des Zahlungsvorgangs den Betrag in einem Fenster anzeigt).
Angehängte Dateien
Dateityp: zip MoneyThroughWire.zip (915,7 KB, 59x aufgerufen)
Kaum macht man's richtig - schon funktioniert's
Zertifikat: Sir Rufo (Fingerprint: ‎ea 0a 4c 14 0d b6 3a a4 c1 c5 b9 dc 90 9d f0 e9 de 13 da 60)
  Mit Zitat antworten Zitat
HeZa

Registriert seit: 4. Nov 2004
Ort: Dortmund
182 Beiträge
 
Delphi 10 Seattle Professional
 
#6

AW: [Beispiel] Wie funktionieren Interfaces?

  Alt 5. Feb 2016, 12:07
Danke für die Beispiele auch noch mal von mir. Insbesondere das zweite Beispiel fand ich sehr erhellend.
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 07:54 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