![]() |
Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Hallo!
Ich entwickle z.Z. eine Komponete. Es handelt sich um ein Pagecontrol, bei dem man Tabs verschieben oder ausblenden kann. Für meine Fragestellung eigentlich nebensächlich, entscheidend ist nur, dass die Einstellungen gespeichert werden können und so dauerhaft bestehen bleiben. Die Komponente arbeitet mit einer Klasse in der die Speicherung der Einstellungen ausgelagert ist. Zur eindeutigen Speicherung (an einem zentralen Ort, Datenbank) benötige ich u.a. eine User-ID aus der Anwendung. Die Anwendung selbst arbeitet mit einer globalen Variabel, in der diese ID abgelegt ist und so an allen Programmstellen verfügbar ist. Die Frage ist nun: Wie ermittle ich am besten die UserID innerhalb der Komponentenklasse, ohne diese dauerhaft mit meiner Anwendung und der o.g. globalen Variabel zu koppeln? Wichtig ist mir dabei, die saubere Trennung zw. Komponente und Anwendung trotzdem zu halten und sämtliche SOLID-Prinzipien ( ![]() Nicht gewünscht ist, jedem einzelnen Pagecontrol-Objekt von außen die ID mitzuteilen, da dieses an unzähligen Programmstellen eingesetzt werden soll. Danke für ein Feedback. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Eine OnGetUserID Procedure in das Object einfügen..
Mavarik |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
und dann?
Ich meine, wenn es eine Event-Zuweisung werden soll, dann muss ich dies Event "OnGetUserId" ja bei jedem Object zuweisen. Das möchte ich nicht aufgrund der vielzahl an Objekten. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Mir fallen 3 Varianten ein, die aber alle letztlich auf das Gleiche hinaus laufen.
1) globale Variable "pcUserId" in der PageControl-Unit anlegen und bei der Initialisierung des Projektes zuweisen. 2) das gleiche als Klassenvariable (weiß nicht, ab welchem Delphi das unterstützt wird) 3) eine Funktionsvariable (oder wie heißt das?) definieren, der man bei der Projektinitialisierung eine Funktion zur Ermittlung der UserId zuweist. (PS: schreib mal Deine Delphi-Version in Dein Profil) |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Hm, irgendwie habe ich das Gefühl das wichtige Informationen oder zumindest ein kleines Codebeispiel vom Ist-Zustand fehlen.
Auf jeden Fall sollte deine visuelle Komponente NICHT diese ID kennen, sondern nur deine Klasse, die für das speichern zuständig ist. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Delphi Version XE3.
Hier ein kleines Codebeispiel (ich war davon ausgegangen, dass dies nicht mehr Aussagekraft hat, als mein engehender Text). Den realen Code kann ich aufgrund des Umfangs schlecht hier unterbringen:
Delphi-Quellcode:
type
TFormMain = class(TForm) PageControlMain: TMyPageControl; TabSheet1: TMyTabSheet; TabSheet2: TMyTabSheet; TabSheet3: TMyTabSheet; procedure FormCreate(Sender: TObject); private { Private-Deklarationen } public { Public-Deklarationen } end; var FormMain: TFormMain; fUserID : integer; //globale User ID implementation {$R *.dfm} procedure TFormMain.FormCreate(Sender: TObject); begin fUserID := GetGlobalUserID; //globale User ID wird von wo auch immer gesetzt bei Programmstart end; (******************************************************************************) type TMyPageControl = class(TComponent) private { Private-Deklarationen } Settings : TControlSettings; public { Public-Deklarationen } end; (******************************************************************************) type TControlSettings = class private { Private-Deklarationen } public { Public-Deklarationen } procedure SaveSettingstoDatabase; end; implementation procedure TControlSettings.SaveSettingstoDatabase; begin //Hier wird die globale UserID benoetigt end; (******************************************************************************) |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Eine einfache Lösung wäre folgende:
Delphi-Quellcode:
type
TControlSettings = class private { Private-Deklarationen } public { Public-Deklarationen } class var UserId: Integer; procedure SaveSettingstoDatabase; end; ... procedure TFormMain.FormCreate(Sender: TObject); begin fUserID := GetGlobalUserID; //globale User ID wird von wo auch immer gesetzt bei Programmstart TControlSettings.UserId := fUserId; end; |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Man kann das Event auch als Class-Property oder über eine Register-Klassenprozedur integrieren, welche den Callback allen Instanzen/Nachkommen der Klasse zur Verfügung stellt.
[edit] siehe #7 und das in der Klasse kommt zwar auf's "Gleiche" raus, wie das in #6, aber diese globalen Variablen sollte man meistens gleich verbieten. [/edit] Falls kein Callbck angegeben wurde, könnte intern problemlos zumindestens eine ID aus GetUserName+GetComputerName erzeugt werden. Irgendwie müsste es auch möglich sein die BenutzerID (GUID) des Benutzerkontos vom Windows zu erfragen. (die wäre theoretisch weltweit eindeutig) |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Einfach in verschiedene Units aufteilen?!? :gruebel:
Delphi-Quellcode:
unit Component;
interface uses System.Classes, SettingsSaver; type TMyPageControl = class(System.Classes.TComponent) private FSettings : TControlSettings; end; implementation end.
Delphi-Quellcode:
unit SettingsSaver;
interface uses System.Classes; type TControlSettings = class(TObject) strict private type TYourDatabase = class(TObject) function Save(AUserID : Integer) : Boolean; end; private FDatabase : TYourDatabase; public function SaveSettingstoDatabase : Boolean; end; implementation uses User; function TControlSettings.SaveSettingstoDatabase : Boolean; begin Result := FDatabase.Save(User.GetUserID) end; function TControlSettings.TYourDatabase.Save(AUserID : Integer) : Boolean; begin Result := AUserID > 0; end; end.
Delphi-Quellcode:
unit User;
interface function GetUserID : Integer; implementation function GetUserID : Integer; begin Result := 1234; end; end. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
Aber da aus deinem Code die tatsächliche Verwendung der UserID nicht hervorgeht (insofern bringt er wirklich nicht mehr als deine ursprüngliche Beschreibung), kann man halt auch keine genaueren Hinweise geben. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Danke erstmal für das zahlreiche Feedback :thumb:
Daraus fasse ich folgende Lösung für mich als am sinnvollsten zusammen:
Delphi-Quellcode:
type
TApplicationUser = class private { Private-Deklarationen } public { Public-Deklarationen } class var UserId: Integer; end; ... type TControlSettings = class private { Private-Deklarationen } User : TApplicationUser; public { Public-Deklarationen } procedure SaveSettingstoDatabase; end; ... procedure TFormMain.FormCreate(Sender: TObject); begin fUserID := GetGlobalUserID; //globale User ID wird von wo auch immer gesetzt bei Programmstart TApplicationUser.UserId := fUserId; end; Allerdings habe ich dabei den Ansatz von Uwe noch nicht beachtet, weil ich ihn noch nicht genau verstanden habe. Um die Dir fehlende Info nachzureichen: In der Methode "SaveSettingsToDatabase" sollen die Einstellungen dann in die Datenbank geschoben werden, also Query-Objekt erzeugen und ein Update/Insert auf eine Tabelle abfeuern. Die UserId bildet dabei den Foreignkey. Über eine weitere Methode Methode "LoadSettingsFromDatabase" wird dieser Datensatz später wieder ausgelesen |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Also du hast da irgendwelche Komponenten und da sollen Einstellungen gelesen und geschrieben werden.
Du brauchst also eine
Delphi-Quellcode:
und
TReader
Delphi-Quellcode:
Klasse, die abstrakt Eigenschaften lesen und schreiben können. Mehr muss die Komponente doch gar nicht wissen, wohin und mit wem kann der doch egal sein.
TWriter
Delphi-Quellcode:
Dann benötigst du eine abstrakte
TMyComponent = class( TComponent )
public procedure ReadSettings( AReader : TReader ); procedure WriteSettings( AWriter : TWriter ); end;
Delphi-Quellcode:
Klasse, die eine
TSettingsStore
Delphi-Quellcode:
und
TReader
Delphi-Quellcode:
-Instanz zur Verfügung stellt.
TWriter
Jetzt werden wir konkreter: Du leitest dir von
Delphi-Quellcode:
eine Klasse ab, die mit einer Datenbank kommunizieren kann und auch eine UserID aufnehmen kann.
TSettingsStore
|
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
dieser Ansatz setzt allerdings voraus, dass
Delphi-Quellcode:
und
ReadSettings
Delphi-Quellcode:
von außen aufgerufen wird, oder?
WriteSettings
Delphi-Quellcode:
Dies ist nämlich nicht der Fall. Die Komponente kümmert sich eigenständig um das Auslösen dieser Ereignisse.
procedure TFormMain.FormCreate(Sender: TObject);
begin ... MyPageControl.ReadSettings(SettingsStore.Reader); ... end; procedure TFormMain.FormClose(Sender: TObject; var Action: TCloseAction); begin ... MyPageControl.WriteSettings(SettingsStore.Writer); ... end; |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Und wo ist jetzt das Problem daraus Events zu machen und entsprechend zu verdrahten?
Irgendwo musst du ansetzen und etwas übergeben, ansonsten legst du dich starr fest. Wo ist egal, und wenn die Komponenten einfach ein "Singleton" ansprechen und sich darüber die Instanz zum Speichern/Laden holen. Diesem Singleton übergibst du dann einfach beim Start der Anwendung die konkrete Instanz und gut. Also mal ganz simpel:
Delphi-Quellcode:
und in der Komponente dann
unit MyUserSettings;
interface type IUserSettingsStorage = interface ['{F3E5657B-39EF-4FA1-A601-8AFCEE50D6D1}'] procedure WriteString( const AName, AValue : string ); function ReadString( const AName, ADefault : string ) : string; end; TUserSettings = class private class var _Storage : IUserSettingsStorage; private class function GetStorage : IUserSettingsStorage; static; class procedure SetStorage( const Value : IUserSettingsStorage ); static; public class property Storage : IUserSettingsStorage read GetStorage write SetStorage; end; implementation { TUserSettings } class function TUserSettings.GetStorage : IUserSettingsStorage; begin // Wenn _Storage NIL, dann könnte man auch eine DUMMY/NULL-Instanz zurückgeben, die einfach nichts macht // dann spart man sich die Überprüfung, ob es eine Instanz gibt Result := _Storage; end; class procedure TUserSettings.SetStorage( const Value : IUserSettingsStorage ); begin _Storage := Value; end; end.
Delphi-Quellcode:
Es gibt da natürlich noch eine ganze Bandbreite an Spielmöglichkeiten.
interface
type TMyComponent = class(...) protected procedure DoLoadUserSettings; procedure DoSaveUserSettings; end; implementation uses MyUserSettings; procedure TMyComponent.DoSaveUserSettings; begin TUserSettings.Storage.WriteString( 'Name', Name ); end; procedure TMyComponent.DoLoadUserSettings; begin Name := TUserSettings.Storage.ReadString( 'Name', Name ); end; Die Storage gibt mir eine spezielle Instanz für die Komponente zurück
Delphi-Quellcode:
etc., etc.
TUserSettings.Storage('TForm1.PageControl1').WriteString('Name',Name);
|
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Das hört sich sehr gut an. So werde ich es mal angehen. Vielen dank an alle für das Feedback!!!
|
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
@Sir Rufo: Naja, ist immer noch gekoppelt, nur nett als Klasse mit class var verpackt, dass es ein global State ist, ändert sich nicht. Imo kann man doch locker ein Settings Objekt/Komponente basteln, die man an das PageControl, was die Settings gerne hätte heften kann, genau wie man normalerweise Komponenten aneinander steckt. Dann hat man sogar den Vorteil, dass man übermorgen, wenn mal jemand die verrückte Idee hat 2 verschiedene PageControls unterschiedlich zu konfigurieren, nicht dumm da steht. P.S.: Weil ich das Video zu dem Thema so gut finde und so gerne verlinke: ![]() |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
Die Komponente an sich braucht und darf die UserId nicht kennen, denn sonst könnte sie ohne nicht leben. Ich würde sogar soweit gehen, das 'LoadFrom(aReader : TReader)' und 'SaveTo(aWriter : TWriter)' aus der Komponente zu entfernen. Die Komponente muss nicht lesen und schreiben können. Wir Menschen können ja auch ![]() Insofern würde ich die Komponente Komponente sein lassen, schlank und so, wie GottProgrammierer sie schuf. Und wenn nun irgendwer meint, die Eigenschaften irgendwohin speichern zu müssen, soll er das doch tun. Das schreit dann geradezu nach einer TReader und TWriterFactory, sodaß diese Factory für jedes Control den passenden Reader/Writer aus dem Hut zaubert. Und da wir die Factory wieder über eine Factory erzeugen lassen können, können wir hier dann unsere speziellen DB-ReaderWriter über eine entsprechende Factory erzeugen lassen. D.h. ich habe dann die Freiheit, heute mal in die Registry und morgen von mir aus in die DB (oder eine Textdatei oder oder oder) zu speichern, wobei nur bei der DB-Variante wirklich ausnahmsweise eine UserID nötig wäre. Bei anderen benötigen wir vielleicht irgendeinen Pfad, oder einen Registry-Schlüssel etc. Das ist dann komplett entkoppelt und die spezielle DB-ReaderWriter-Factory kennt dann die UserID und gibt sie bei der Produktion einer Klasse dieser eben mit. Und somit wäre auch das don't-use-globals gelöst, denn global ist die User-ID dann ja nun nicht mehr. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Liste der Anhänge anzeigen (Anzahl: 1)
Die letzten Antworten haben mich nun zugegebener Weise mehr verwirrt als geholfen. Das liegt aber wohl zuletzt daran, dass ich noch nicht ganz folgen kann.
Ich habe mir mal die Mühe gemacht, das Problem in einem Demoprojekt nachzubauen. Siehe Anhang. Dort gibt es: 1. Anwendung: das RememberProject besteht aus den Formularen Main und Detail. Beim Start des FormMain wird quasi als Login eine UserId abgefragt und global gespeichert. Dies simuliert an der Stelle den Bestandscode. Sicher nicht ganz optimal, aber halt der Ist-Zustand. Das FormDetail bietet nun als Komponente ein Edit (TRememberEdit) das sich je UserId den eingegebenen Text merkt (ganz tolles Feature :-) ). 2. Komponente: im RememberPackage ist die o.g. TRememberEdit Komponente enthalten. Über die Klasse TControlSettings wird das speichern/lesen realisiert. Diese wiederum arbeitet mit TApplicationUser um von dort die User-Id zu holen. Soweit mein Lösungsansatz. Jetzt würde mich interessieren, ob mir jemand die „saubere“ Lösung als Alternative präsentieren kann. @Stevie: besonders Deine Lösung würde mich interessieren! Deine Denkansätze beim Delphi-Code-Camp haben mir grundsätzlich gut gefallen, nur hapert es mir hier im konkreten Beispiel bei der Umsetzung... Vielen Dank! |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Wenn du auf die globale Variable der User-ID im Hauptformular (Main.pas) verzichtest und im Unterformular (Detail.pas) nicht das Hauptformular inkludierst, sondern gleich ApplicationUser, wird das eine runde Sache.
Außerdem kann man RememberEdit noch ein bisschen straffen, indem man die Settings-Klasse als Member-Variable anlegt. So kommt es wegen jeder kleinen Änderung nicht ständig zum Erschaffen und Zerstören der TControlSettings-Klasse. Sicher gibt es noch ein paar andere und ausgefallende Ansätze, aber im Wesentlichen hast du damit erreicht was du wolltest: Die Komponente kennt nicht mehr die UserID. Ggf. könnte man die UserID per Property o.ä. an das Unterformular (Detail.pas) übergeben, dann muss man hier auch nichts mehr inkludieren. Also weder Main noch ApplicationUser.
Delphi-Quellcode:
unit Main;
interface uses System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TFormMain = class(TForm) Button1 : TButton; procedure Button1Click(Sender : TObject); procedure FormShow(Sender : TObject); private function GetUserId : integer; function CheckUserId : boolean; public end; var FormMain : TFormMain; implementation {$R *.dfm} uses Detail, ApplicationUser; procedure TFormMain.Button1Click(Sender : TObject); var FormDetail : TFormDetail; begin FormDetail := TFormDetail.Create(Self); try FormDetail.ShowModal; finally FreeAndNil(FormDetail); end; end; function TFormMain.CheckUserId : boolean; begin Result := TApplicationUser.UserId >= 0; if not Result then begin MessageDlg('UserID ungültig', mtError, [mbOk], 0); end; end; procedure TFormMain.FormShow(Sender : TObject); begin TApplicationUser.UserId := GetUserId; if not CheckUserId then Application.Terminate; Self.Caption := 'Login mit UserID=' + IntToStr(TApplicationUser.UserId); end; function TFormMain.GetUserId : integer; var UserString : string; begin Result := 0; UserString := InputBox('User-ID', 'Bitte die User-ID eingeben:', '0'); Result := StrToIntDef(UserString, 0); end; end.
Delphi-Quellcode:
unit Detail;
interface uses System.SysUtils, System.Classes, Vcl.Controls, Vcl.Forms, Vcl.StdCtrls, RememberEdit; type TFormDetail = class(TForm) Label1 : TLabel; RememberEdit1 : TRememberEdit; lblHallo : TLabel; procedure FormCreate(Sender : TObject); private public end; implementation {$R *.dfm} uses ApplicationUser; procedure TFormDetail.FormCreate(Sender : TObject); begin lblHallo.Caption := lblHallo.Caption + ' ' + IntToStr(ApplicationUser.TApplicationUser.UserId); end; end.
Delphi-Quellcode:
unit RememberEdit;
interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Vcl.StdCtrls, ControlSettings; type TRememberEdit = class(TEdit) private FControlSettings : TControlSettings; procedure SetTextToSettings(const Value : string); function GetTextFromSettings : string; public constructor Create(AOwner : TComponent); override; destructor Destroy; override; end; procedure Register; implementation procedure Register; begin RegisterComponents('RememberTest', [TRememberEdit]); end; constructor TRememberEdit.Create(AOwner : TComponent); begin inherited; FControlSettings := TControlSettings.Create; Self.Text := GetTextFromSettings; end; destructor TRememberEdit.Destroy; begin SetTextToSettings(Self.Text); FControlSettings.Free; inherited; end; function TRememberEdit.GetTextFromSettings : string; begin Result := ''; if (csDesigning in ComponentState) then exit; Result := FControlSettings.Read; end; procedure TRememberEdit.SetTextToSettings(const Value : string); begin if (csDesigning in ComponentState) then exit; FControlSettings.Write(Value); end; end. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Delphi-Quellcode:
Ich würde da 'Release' verwenden. Soweit ich mich erinnere, ist das der bevorzugte Weg, ein Windows-Formular freizugeben.
... finally
FreeAndNil(FormDetail); end; Und nochwas: Die Logik von CheckUserId gehört nicht ins Login/Hauptformular, imho. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
Allerdings genügt in diesem Fall auch ein simples
Delphi-Quellcode:
FormDetail.Free;
|
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
|
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:
Ist noch nicht die 100%ige Lösung, da mir die Abhängigkeit vom Detailform auf das Mainform noch nicht gefällt (siehe Kommentar im Source). Außerdem wird durch das Fehlen einer Abstrakten Settings Klasse noch die konkrete Implementierung vom TRememberEdit benötigt - da sollte man dann noch eine abstrakte Klasse implementieren (ich weiß grad nicht, ob man auch Interfaces im OI verdrahten kann). Den noch etwas unsauberen Code im Mainform hab ich mal so belassen, denn darum ging es ja nicht hauptsächlich. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
Geht es nur darum, dass fancy im OI zu verdrahten? Da fand ich meine Lösung sauberer. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
a.1) Lass das TFormDetail.FormCreate ein
Delphi-Quellcode:
ausführen
Settings.Load(RememberEdit)
a.2) Schreib eine TSettings-Komponente, klatsch die auf das TFormDetail und füge die 'RememberEdit'-Komponente der TSettings-Komponente zu (so macht das DevExpress mit seinem TPropertyStore oder wie das Teil heißt). b) So, wie Du das schon angedacht hast: Settings => Datamodule Ich find a besser, denn die Lösung geht mit allen Komponenten. Bei eurer bisherigen Lösung muss man zwangsweise für jede Komponente, die ihre Settings persistiert, eine Ableitung machen :wall: in meinen Augen. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
Die Detailform kennt es, weil die Main.pas im uses steht und man dann über RAD Mechanismen an das RememberEdit das Settings dingen eines anderen Moduls hängen kann - manche Leute brauchen das, daher mein Kommentar - ich brauch sowas persönlich nich, da es nur funktioniert, wenn man diese globalen Variablen in den Units stehen lässt. Ich würd des Setting als Property meines Detailforms machen und beim setzen die Properties anwenden. Braucht halt mehr Code, da das nicht automagically im Loaded passiert. Zitat:
|
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
|
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
|
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
Ich finde meine Variante einfach wiederverwendbarer und praktischer, weil, wie schon gesagt, keine SonderlockenIchKannMitSettingskomponenten erstellt werden müssen. So ein Settingsdingens kann mit Jedem. Variante a.2) ist eben Old-School Delphi: "Für alles ne Komponente". Einfach, RAD, Chaos. Allerdings muss man sich auch genau überlegen, was man da eigentlich umsetzen will. Eine spezielle(!) Edit-Komponente, die ihre letzte Eingabe persistieren will, ist anders umzusetzen, als die Fähigkeit, beliebige Eigenschaften von Komponenten zu persistieren. Soweit ich mich erinnere, ging es im Eingangspost um ein PageControl, das Eigenschaften mit Hilfe einer User-ID als Schlüssel in einer DB ablegen will und da dachte ich, wir sprechen über Letzteres. Im Beispiel mit dem RememberEdit wäre vermutlich die hier vorliegende Variante passender. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
Grundsätzlich stimme ich dir zu, was das Speichern/Laden der Settings angeht (Stichwort SRP). Aber auch dann muss man entweder loader/saver haben, die speziell für unterstützten Komponenten sind oder die Komponenten müssen über irgendeinen Mechanismus (z.B. Attribute) mitteilen, was sie denn gespeichert haben möchten. Ansonsten artet das Settingsdingen irgendwann in einem Riesengroßen if then else Chaos aus, in dem überprüft wird, welche Komponente ich denn gerade speichern möchte inklusive der abhängigkeit des Settingsdingen auf alles, was es kann. Dennoch: Was wollen wir erreichen? Lose kopplung, um einzelne Teile zu testen ohne den gesamten Klump deiner Anwendung dran hängen zu haben. Sofern man das Settingsdingen noch abstrahiert, so dass ein TRememberEdit nicht mit der konkreten Implementierung arbeitet sondern mit einer Abstraktion habe ich das erreicht. Ebenfalls ist mein konkretes TSettings nicht von irgendwelchen global States abhängig und könnte auch ohne UserId seine Settings in localappdata oder sonstwo hin ballern. Wie und wo ich dann diese beiden Kollegen miteinander bekannt mache ist fast nebensächlich. Zitat:
|
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
Delphi-Quellcode:
Um es kurz zu machen: Deine Lösung ist vollkommen ausreichend, ich wollte noch das SRP einbringen, das ist alles. Wie man den Rest löst (muss ich dir ja nicht erzählen), hatte ich
Procedure TFormDetail.FormCreate(Sender : TObject);
Begin myDataModule.Settings.Load(RemberEdit); // bzw. For control in Controls do myDataModule.Settings.Load(control); End; ![]() Zitat:
Will ich (nur) eine Komponente, die ihren inneren Zustand irgendwo persistiert, oder will ich eine generelle Möglichkeit, Komponenten bzw. Klassen zu persistieren? Im ersten Fall ist die Persistierung Bestandteil der Komponente ("Wer ist zuständig?" "Die Komponente!") Im zweiten Fall ist die Persistierung Bestandteil meines Frameworks, mit allen Vorteilen. Mit meinem Fokus auf die Eingangsfrage war nicht das Pagecontrol gemeint (das ist wirklich wurst), sondern die für mich dann doch im Vordergrund stehende Möglichkeit, Komponenten generell zu persistieren. So hatte ich die Frage jedenfalls verstanden. Und dann wäre dein Vorschlag eben verbesserungswürdig. Das kannst Du natürlich anders sehen, wenn Du magst. |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Zitat:
Nu hab ich in jedem Form, wo ich meine Settings laden/speichern möchte als Abhängigkeit das myDataModule - nicht falsch verstehen, das hab ich in der Lösung, die ich oben gezeigt habe, auch. Nur das als irgendwie besser zu verkaufen ist Augenwischerei, da es dasselbe in grün ist. Deshalb stell ich mal eine Behauptung auf bis mich jemand vom Gegenteil überzeugt: "DI ist die einzige Möglichkeit, Abhängigkeiten voneinander zu entkoppeln." Ob man das immer muss oder manchmal auch der halbe Weg reicht, steht wie so oft bei solchen Diskussionen auf einem anderen Blatt. ;) |
AW: Cleancode, Dependency Injection oder wie stelle ich mich richtig an
Da ich die Abhängigkeit von den Controlsettings nur noch an einem Pattern habe (FormCreate), anstatt in zwei (FormCreate und jede Komponente), kann ich das nun durch Vererbung wegkürzen.
Bei meiner Lösung habe ich die Abhängigkeit zunächst aus der Komponente entfernt und mit einer abstrakten Basisklasse wäre die Abhängigkeit an genau einer Stelle. Und wenn ich sie nur dort verwende, kann ich auf das Globalgedöns auch ganz verzichten. Ganz ohne Abhängigkeiten von den Controlsettings *kann* es gar nicht funktionieren, weil die Verwendung Teil der Lösung ist. Ergo haben wir irgendwo mindestens ein 'uses ControlSettings'. Je weniger Stellen, desto besser. Ganz Ohne geht nicht, mehr als eine Abhängigkeit ist aber beinhae schon zuviel, also:
Delphi-Quellcode:
So würde ich das lösen. Natürlich sind die Formulare in einzelnen Dateien. Habe das nicht SOLID-mäßig abgeklopft, aber von meinem alten Bierbauchgefühl würde ich sagen, das es ausreichend ordentlich ist.
Type
TBaseControlSettingsForm = Class(TForm) Private Procedure LoadSettings; Public Procedure FormCreate(Sender : TObject); // lädt die Properties der Controls über die ControlSettings-Klasse end; ... // TFormDetail = Class (TBaseControlSettingsForm); // TFormMain = Class (BaseControlSettingsForm); Procedure TBaseControlSettingsForm.FormCreate(..); Begin LoadSettings; End; Ich kann der BasisForm auch eine 'UserID' spendieren, sofern diese systemimmanent ist, also integraler und unverzichtbarer Bestandteil des (Form-)Frameworks. In meinem Ansatz ist z.B. die Verwendung der Controlsettings systemimmanent, weil eben genau so gewollt. Übrigens, wenn man DI konsequent verwendet, tippt man sich einen Wolf und erzeugt wunderbar testbaren Code, der aber so grottig (weil schlecht lesbar), das man kotzen könnte. Den Weg muss man aber dort gehen, wo Abhängigkeiten durch Faulheit entstehen würde. Aber wenn die Abhängigkeit -tolles Wort- systemimmanent (und nur dann) ist, sollte man den DI-Firlefanz außen vor lassen. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 22:31 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