|
Antwort |
Registriert seit: 5. Jan 2005 Ort: Stadthagen 9.454 Beiträge Delphi 10 Seattle Enterprise |
#1
Im Anhang findet man unterschiedliche Arten die Interfaces
Delphi-Quellcode:
zu implementieren.
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. Mit dem Konsolen-Programm
Delphi-Quellcode:
kann man dann sehen, welche Unterschiede in den einzelnen Implementierungen bestehen und welche Klasse konkret die Information liefert.
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. IFoo.GetFooInfo und IBar.GetBarInfo liefern den Klassen-Namen und den Methoden-Namen zurück:
Code:
P.S. Von den ganz schrägen Implementierungen habe ich jetzt mal Abstand genommen.
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
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) |
Zitat |
Registriert seit: 11. Apr 2009 557 Beiträge Delphi 12 Athens |
#2
Tolle Sache, wirklich verdienstvoll, meinen Dank hast du.
|
Zitat |
Registriert seit: 26. Nov 2003 Ort: Halle/Saale 4.343 Beiträge Delphi 11 Alexandria |
#3
P.S. Von den ganz schrägen Implementierungen habe ich jetzt mal Abstand genommen.
Stahli
http://www.StahliSoft.de --- "Jetzt muss ich seh´n, dass ich kein Denkfehler mach...!?" Dittsche (2004) |
Zitat |
Registriert seit: 15. Mär 2007 4.093 Beiträge Delphi 12 Athens |
#4
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 |
Zitat |
Registriert seit: 5. Jan 2005 Ort: Stadthagen 9.454 Beiträge Delphi 10 Seattle Enterprise |
#5
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:
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:
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.
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. Das war ja schon mal einfach, da können wir uns gleich an die Oberfläche machen:
Delphi-Quellcode:
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.
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. 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:
Delphi-Quellcode:
Einfach einen Thread starten, 2 Sekunden warten und dann synchronisiert den Callback mit der Meldung aufrufen.
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. Und wie kriegen wir die jetzt zusammen? Mit einer Konfiguration:
Delphi-Quellcode:
Und in der DPR Datei rufen wir einfach diese procedure
auf
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.
Delphi-Quellcode:
Das ist ja schon mal ganz nett ... die Oberfläche kann man jetzt testen, ob die bei einem Erfolg alles richtig anzeigt.
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. 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:
und einen Service, der mit diesem Formular arbeitet:
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.
Delphi-Quellcode:
Jetzt noch die Konfiguration anpassen
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.
Delphi-Quellcode:
Und in der DPR natürlich auch die richtige procedure
aufrufen.
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.
Delphi-Quellcode:
Schon können wir jeden Fall in der Anwendung testen, obwohl wir noch nicht ein Stück Code zum konkreten ZahlungsService haben.
begin
// Configuration.SimpleConfiguration; Configuration.SimpleDialogConfiguration; Application.Initialize; Application.MainFormOnTaskbar := True; Application.CreateForm(TMainForm, MainForm); Application.Run; end. 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).
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) |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs 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
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |