![]() |
AW: Interfaces + Factorys
Ok, super!
Das IMotorBetrieben ist eine bessere Bezeichnung als IHasMotor. Aber konzeptionell meinte ich das Gleiche. Allerdings reicht Supports(aContext, IMotorBetrieben) für einen Zugriff auf den Motor noch nicht ganz aus, weil der ja nil sein könnte. Also muss dazu ggf. nochmal eine Prüfung her. Wenn man ganz mutig denkt, könnte ein späterer Fluxkompensator natürlich zu unserer Motor-Schnittstelle nicht mehr kompatibel sein. Aber ok, das muss man wohl akzeptieren und in 20 Jahren die Schnittstellen ggf. nochmal überarbeiten. ;-) Die Enten und Fahrzeuge finde ich aber im Zusammenhang mit Schnittstellenbeispielen dennoch nicht schlecht, da man sieht, dass völlig unterschiedliche Klassen (ohne direkte gemeinsame Basisklasse) die gleichen Schnittstellen bedienen können. |
AW: Interfaces + Factorys
Bei einigen Aktionen solltest du überlegen, ob sie wirklich zu der Klasse gehören.
Zum Beispiel die Methode Tanken gehört sicherlich nicht in die Klasse Motor oder Auto oder Boot. Diese Klassen/Interfaces sind nicht die Akteure, sondern die Argumente für die Aktion betanken, welche ein IBetankbar Interface oder sowas haben. |
AW: Interfaces + Factorys
Wenn
Delphi-Quellcode:
zulässig ist, dann hat man das
nil
![]() Was richtig ist hängt vom Gesamtkontext ab und kann pauschal nicht beantwortet werden. Ich würde das aber zunächst völlig herauslassen und eben immer bei
Delphi-Quellcode:
einen Motor voraussetzen.
IMotorBetrieben
|
AW: Interfaces + Factorys
Um Interfaces näher zu bringen würde ich eben ganz andere Beispiele nehmen, etwas, das man anfassen kann.
Wir haben z.B. eine Anwendung, die Dateien in einen Stream liest, die dann von der Anwendung weiterverarbeitet werden. Dazu hat man sich eine schöne Klasse gebaut, die nun eine Datei in einen Stream einliest und diesen zurückgibt.
Delphi-Quellcode:
Vor allem ist das schick, wenn wir die Datei "$(Archiv1)\12345.doc" lesen wollen, setzt diese Klasse das gleich in den korrekten Pfad um :stupid: Sehr geschmeidig.
TFileStreamHandler = class
procedure GetStream( AFilename : string; AStream : TStream ); procedure WriteStream( AFilename : string; AStream : TStream ); end;
Delphi-Quellcode:
Alles ist gut, bis zu dem Tag, wo man feststellt, dass es besser wäre, diese Streams nicht wirklich in Dateien, sondern in einer Datenbank zu halten. Kein Problem, ändere ich einfach die Klasse ... hmmm, wie machen wir das bloss. Diese Klasse benutzen wir an jeder Stelle im Programm für jede Datei, auch für die, die weiter als Datei dort liegen.
procedure Foo( AHandler : TFileStreamHandler );
var LStream : TStream; begin AHandler.GetStream( '$(Import)\Bild.jpg', LStream ); AHandler.WriteStream( '$(Archiv)\123456.jpg', LStream ); end; Ganz einfach mit Interfaces:
Delphi-Quellcode:
Die Anwendung ändere ich um, damit die mit dem Interface arbeitet
IStreamHandler = interface
procedure GetStream( AFilename : string; AStream : TStream ); procedure WriteStream( AFilename : string; AStream : TStream ); end; // die alte Klasse tut es ja noch TFileStreamHandler = class( TInterfacedObject, IStreamHandler ) procedure GetStream( AFilename : string; AStream : TStream ); procedure WriteStream( AFilename : string; AStream : TStream ); end;
Delphi-Quellcode:
Jetzt benötigen wir eine neue Klasse, die uns den Dateiinhalt aus der Datenbank holt/schreibt
procedure Foo( AHandler : IStreamHandler );
var LStream : TStream; begin AHandler.GetStream( '$(Import)\Bild.jpg', LStream ); AHandler.WriteStream( '$(Archiv)\123456.jpg', LStream ); end;
Delphi-Quellcode:
Und jetzt noch eine, die den Zugriff routet
TDatabaseStreamHandler = class( TInterfacedObject, IStreamHandler )
procedure GetStream( AFilename : string; AStream : TStream ); procedure WriteStream( AFilename : string; AStream : TStream ); end;
Delphi-Quellcode:
Und erzeuge die Klasse z.B. mit
TRoutedStreamHandler = class( TInterfacedObject, IStreamHandler )
procedure GetStream( AFilename : string; AStream : TStream ); procedure WriteStream( AFilename : string; AStream : TStream ); end;
Delphi-Quellcode:
Welchen noch so ominösen Stream-Speicherort ich mir auch noch ausdenken werde (oder mir ausgedacht wird) ich erzeuge eine konkrete Klasse für diesen konkreten Ort und klatsche das in den Router mit rein. Die Anwendung arbeitet ohne weitere Änderungen wie gehabt weiter.
function GetStreamHandler : IStreamHandler;
var LFiles : IStreamHandler; LDatabase : IStreamHandler; begin LFiles := TFileStreamHandler.Create; LDatabase := TDatabaseStreamHandler.Create( 'Server1', 'user', 'pass', 'archivdb' ); Result := TRoutedStreamHandler.Create( {Default} LFiles, {} [ {} 'Archiv', LDatabase, {} 'Archiv1', LDatabase ] ); end; |
AW: Interfaces + Factorys
Das Beispiel mit dem StreamHandler ist derart unverständlich, das ich empfehle, den Post zu entfernen. Das läßt Leute, die so unsicher auf dem Gebiet sind wie ich, gleich wieder an sich zweifeln.
|
AW: Interfaces + Factorys
Den letzten Abschnitt verstehe ich auch nicht.
|
AW: Interfaces + Factorys
Der StreamHandler definiert (so wie ich das verstanden habe) nur den Vertrag, das Daten mit einem Namen versehen in einen Stream geschrieben und aus ihm gelesen werden.
Wieso weiß der IStreamHandler eigentlich, das es Filenamen gibt? Sollte ihm das nicht komplett egal sein, ob es ein File-, Registry-, Tabellen-, oder Kleinkolonieseiteneingangs-namen handelt? Du hast geschrieben, das ihr das erst später bemerkt habt. Klar, hinterher ist man immer schlauer. Daher als Tipp (für alle Leser): Beim Design von Klassen und Interfaces sollten nur gerade so viele Informationen in das Design gesteckt werden, wie die Klasse/das Interface benötigt. Wenn also ein 'IStreamHandler' deklariert wird, reich es, einen 'Name' (oder treffender, falls ich das richtig verstanden habe: 'Identifier') anzugeben. Einfach deshalb, weil im Namen des Interfaces nirgends etwas mit 'File' steht. Ansonsten halte ich das Beispiel für sehr anschaulich. Es ist auch ein tolles Beispiel, wie sehr man später davon profitiert, früher mal etwas richtig gemacht zu haben (der Name lässt sich ja leicht mit Refactoring noch zurechtrücken). PS: Was der TRoutedStreamHandler macht, erschließt sich mir auch nicht. Aber man kann z.B. einen Streamhandler für TCP schreiben, oder für eine INI-Datei, oder oder oder. |
AW: Interfaces + Factorys
Bin gerade noch auf eine kleine Eigenart gestoßen...
Folgendes kompiliert und funktioniert problemlos:
Delphi-Quellcode:
Man kann also die Property in der Klasse weglassen (Getter bzw. Setter reichen) und kann die Property dennoch benutzen (auch die Codevervollständigung bietet sie an).
IFlieg = interface
['{0E839812-DAB3-47F0-AF3E-AB05FFDD6CE6}'] procedure Flieg; function get_X: Integer; procedure set_X(Value: Integer); property X: Integer read get_X write set_X; end; TVogel = class(TInterfacedObject, IFlieg) private fX: Integer; public procedure Flieg; function get_X: Integer; procedure set_X(Value: Integer); //property X: Integer read get_X write set_X; end; Ich denke mal, der besseren Lesbarkeit halber sollte man sie dennoch in der Klasse deklarieren. |
AW: Interfaces + Factorys
Zitat:
|
AW: Interfaces + Factorys
Zitat:
Sofern man sich an die Regeln hält, dass die Implementierungen der Interfaces sich gemäß der Spezifikationen verhalten, ist es unerheblich, was sie intern machen. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 19:15 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