![]() |
Problem mit Klassendesign
Hallo,
bis jetzt habe ich objektorientierte Programmierung eher vermieden. Doch für ein aktuelles Projekt hätte ich gerne es eingesetzt, leider scheitert es bei mir an den Grundlagen und ist vermutlich ziemlich trivial. :oops: Da mir die Begriffe für mein Problem fehlen, versuche ich meine Frage zu beschreiben:
Delphi-Quellcode:
Ich habe aktuell eine Anwendung, das einerseits Daten von einer lokalen MySQL-Datenbank ausliest und andererseits über PHP Tunnel Daten aus einer MySql-Datenbank ausliest. Für die Datenbankkommunikation habe ich 2 verschiedene Objektklassen erstellt. Doch wie kann man eine Procedure/Funktion (in diesem Fall "procedure ZeigeInGui") bauen, das als Parameter beide Objektklassen (TMySQL u. TMySQLPhpTunnel) akzeptiert. Später sollte auch ein Objekt für MSSQL dazukommen. Wie kann man sowas machen?
type
TDatabase = class(TObject) public procedure ConnectDB(host,user,pw: String); procedure SelectDB(DBName:String); procedure SQLQuery(Query:String; var Cols:TCols; var Rows:TRows); end; TMySQL = class(TDatabase) public procedure ConnectDB(host,user,pw: String); procedure SelectDB(DBName:String); procedure SQLQuery(Query:String; var Cols:TCols; var Rows:TRows); end; TMySQLPhpTunnel = class(TDatabase) public procedure ConnectDB(host,user,pw: String); procedure SelectDB(DBName:String); procedure SQLQuery(Query:String; var Cols:TCols; var Rows:TRows); end; implementation procedure ZeigeInGui(SQL: TDatabase); //<----- wichtig ist hier! var i,j: Integer; begin SQL.SQLQuery('SELECT * FROM TestTabelle'); for i:=0 to High(Rows[0]) for j:=0 to High(Rows) do StringGrid1.Cells[i,j] rows[i,j]; //nur exemplarisch end; lg, jus |
AW: Problem mit Klassendesign
Genau so.
|
AW: Problem mit Klassendesign
Sieht doch schon gut aus. Es fehlen nur
Delphi-Quellcode:
und
virtual
Delphi-Quellcode:
an den entsprechenden Methoden, damit auch die abgeleiteten Versionen benutzt werden.
override
|
AW: Problem mit Klassendesign
Hallöle...:P
oder über ein Interface. :wink: Sinngemäß: Der Code arbeitet mit dem Interface. Welche "Klasse" das Interface "interpretiert" ist dem Code wurscht... :zwinker: Eigentlich ein Klassiker wie man es verwendet wenn verschiedene DBMS Verwendung finden. :wink: |
AW: Problem mit Klassendesign
|
AW: Problem mit Klassendesign
Die eingestreuten "abstract"s ersparen einem leere Methoden-Hülsen zu erzeugen.
Delphi-Quellcode:
type
TDatabase = class abstract public procedure ConnectDB(host,user,pw: String); virtual; abstract; procedure SelectDB(DBName:String); virtual; abstract; procedure SQLQuery(Query:String; var Cols:TCols; var Rows:TRows); virtual; abstract; end; TMySQL = class(TDatabase) public procedure ConnectDB(host,user,pw: String); override; procedure SelectDB(DBName:String); override; procedure SQLQuery(Query:String; var Cols:TCols; var Rows:TRows); override; end; TMySQLPhpTunnel = class(TDatabase) public procedure ConnectDB(host,user,pw: String); override; procedure SelectDB(DBName:String); override; procedure SQLQuery(Query:String; var Cols:TCols; var Rows:TRows); override; end; implementation procedure ZeigeInGui(SQL: TDatabase); //<----- wichtig ist hier! var i,j: Integer; begin SQL.SQLQuery('SELECT * FROM TestTabelle'); for i:=0 to High(Rows[0]) for j:=0 to High(Rows) do StringGrid1.Cells[i,j] rows[i,j]; //nur exemplarisch end; |
AW: Problem mit Klassendesign
Ich persönlich möchte als weiteren Vorschlag das Entwurfsmuster Factory einbringen.
|
AW: Problem mit Klassendesign
Zitat:
Gruß K-H |
AW: Problem mit Klassendesign
Zitat:
Delphi-Quellcode:
type
IDatabase = interface ['{74C42FE6-FDBD-477E-A74E-CC5841CC8A30}'] procedure ConnectDB(host,user,pw: String); procedure SelectDB(DBName:String); virtual; procedure SQLQuery(Query:String; var Cols:TCols; var Rows:TRows); end; TMySQL = class(TInterfacedObject, IDatabase) public procedure ConnectDB(host,user,pw: String); procedure SelectDB(DBName:String); procedure SQLQuery(Query:String; var Cols:TCols; var Rows:TRows); end; TMySQLPhpTunnel = class(TInterfacedObject, IDatabase) public procedure ConnectDB(host,user,pw: String); procedure SelectDB(DBName:String); procedure SQLQuery(Query:String; var Cols:TCols; var Rows:TRows); end; implementation procedure ZeigeInGui(SQL: IDatabase); //<----- wichtig ist hier! var i,j: Integer; begin SQL.SQLQuery('SELECT * FROM TestTabelle'); for i:=0 to High(Rows[0]) for j:=0 to High(Rows) do StringGrid1.Cells[i,j] rows[i,j]; //nur exemplarisch end; |
AW: Problem mit Klassendesign
Zitat:
Lg, jus |
AW: Problem mit Klassendesign
@TBx
Danke! Ich hatte ein eigenes Interface für jede DB erwartet. Werde mir das mal bei Gelegenheit anschauen. Gruß K-H |
AW: Problem mit Klassendesign
Zitat:
|
AW: Problem mit Klassendesign
Ich habe jetz mal eine kleine Demo für so eine Factory gebaut, vielleicht lichtet das die Verständnisprobleme ein wenig. Stevie kann das zwar mit Sicherheit besser als ich, aber meins funktioniert bislang auch ganz manierlich. Zunächst also das Interface, um das es gehen soll:
Delphi-Quellcode:
Das ist auch das einzige, was alle beteiligten Parteien später kennen werden. Jetzt kommt auch schon der schwierigste Teil, die Factory. Es handelt sich dabei um einen Singleton mit 2 öffentlichen Methoden: Registrierung einer das o.a. Interface implementierenden Klasse für ein String-Kriterium und Rückgabe einer Instanz einer der registrierten Klassen anhand eines String-Kriteriums. Letzteres ist eine recht haarige Angelegenheit, da man möglichst den passenden Konstruktor der jeweiligen Klasse ermitteln muss. Ein einfaches Create ruft nämlich den Konstruktor von TObject auf, der nützt uns herzlich wenig. Ich gehe daher per RTTI die Methoden der Klasse durch und suche nach dem ersten Konstruktor. Besitzt dieser Parameter, werden diese einfach mit Null-Entsprechungen befüllt, anschließend wird der Konstruktor dann aufgerufen.
unit TierIntf;
interface type ITier = interface ['{9DC595F6-9026-4C4B-9FAC-5CCC5437C8A5}'] procedure GibLaut; end; implementation end.
Delphi-Quellcode:
Jetzt noch eine Beispielklasse. Diese muss (natürlich) das Interface kennen, das sie implementieren soll, sowie die Factory, an der sie sich registriert. Die Registrierung selbst nehme ich im Initialization-Abschnitt vor, dann genügt das Einbinden der Unit, um die Klasse bekannt zu machen.
unit TierFactory;
interface uses System.Generics.Collections, System.SysUtils, System.Rtti, TierIntf; type EMismatchingClass = class(Exception); TTierFactory = class abstract private class var FCollection: TDictionary<string, TClass>; class constructor Create; class destructor Destroy; public class procedure RegisterClassFor(Description: string; AClass: TClass); static; class function GetRegisteredInstanceFor(Description: string): ITier; static; end; implementation { TTierFactory } class constructor TTierFactory.Create; begin FCollection := TDictionary<string, TClass>.Create; end; class destructor TTierFactory.Destroy; begin FCollection.Free; end; class function TTierFactory.GetRegisteredInstanceFor (Description: string): ITier; var TheClass: TClass; TheInstance: TObject; Tier: ITier; ctx: TRttiContext; AType: TRttiType; Value: TValue; TheMethod: TRttiMethod; ParamList: TList<TValue>; TheParam: TRttiParameter; begin Result := nil; if FCollection.TryGetValue(AnsiLowerCase(Description), TheClass) then begin ctx := TRttiContext.Create; try AType := ctx.GetType(TheClass); for TheMethod in AType.GetMethods do if TheMethod.IsConstructor then begin ParamList := TList<TValue>.Create; try for TheParam in TheMethod.GetParameters do begin TValue.Make(0, TheParam.ParamType.Handle, Value); ParamList.Add(Value); end; TheInstance := TheMethod.Invoke(TheClass, ParamList.ToArray).AsObject; Supports(TheInstance, ITier, Tier); Result := Tier; finally ParamList.Free; end; break; end; finally ctx.Free; end; end; end; class procedure TTierFactory.RegisterClassFor(Description: string; AClass: TClass); begin if not Supports(AClass, ITier) then raise EMismatchingClass.CreateFmt ('Klasse %s implementiert das ITier-Interface nicht', [AClass.ClassName]); FCollection.AddOrSetValue(AnsiLowerCase(Description), AClass); end; end.
Delphi-Quellcode:
Für weitere Tierarten kann man diese Unit einfach kopieren und alle Vorkommen von "Hund" einfach durch "Katze", "Vogel", "Fisch" oder sonstwas ersetzen.
unit Hund;
interface uses TierIntf, TierFactory; type THund = class(TInterfacedObject, ITier) public procedure GibLaut; end; implementation uses Dialogs; { THund } procedure THund.GibLaut; begin ShowMessage('Ich bin ein ' + ClassName); end; initialization TTierFactory.RegisterClassFor('Hund', THund); end. Zum Schluss noch das Hauptprogramm. Die MainUnit muss das Interface und die Factory kennen, aber nicht die zu instanzierenden Klassen. Ich habe einfach ein Edit und einen Button auf das Formular geklatscht.
Delphi-Quellcode:
Das war alles, wer grobe Fehler finden sollte, bitte korrigieren.
unit TestMain;
interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls; type TfrmFactoryTest = class(TForm) edtTierart: TEdit; btnInstanz: TButton; procedure btnInstanzClick(Sender: TObject); private { Private-Deklarationen } public { Public-Deklarationen } end; var frmFactoryTest: TfrmFactoryTest; implementation {$R *.dfm} uses TierFactory, TierIntf; procedure TfrmFactoryTest.btnInstanzClick(Sender: TObject); var Tier: ITier; begin Tier := TTierFactory.GetRegisteredInstanceFor(edtTierart.Text); if Assigned(Tier) then Tier.GibLaut else ShowMessage(Format('Keine registrierte Klasse für "%s" gefunden', [edtTierart.Text])); end; end. |
AW: Problem mit Klassendesign
Im Prinzip ist das alles so wie ich es schon kannte und auch verstanden habe. Allerdings bleibt es dann nicht aus, im Hauptprogramm immer einen entsprechenden String zu ändern wenn man im Falle von jus immer eine andere Datenbank nutzen möchte.
Dann könnte ich ja im Prinzip direkt die Instanz erzeugen und wäre genau so weit. Einziger Nachteil wäre dann, dass sich der Klassenname nie ändern dürfte, da dieser dann nicht mehr im Hauptprogramm gefunden würde. |
AW: Problem mit Klassendesign
Dieser String kann aber auch genausogut aus einer Konfigurationsdatei kommen (IniFile, XML, Registry, whatever). Im Hauptprogramm ändert sich dadurch nicht eine Zeile, aber die zu verwendende Datenbank kann so flexibel fesgelegt werden.
|
AW: Problem mit Klassendesign
Hmm..
@DeddyH Danke für deine Demo.. Jetzt weiß ich endlich, was Ihr immer mit einer Factory meint ;) Das ich das (nur ohne Interfaces) für Formulare in meinen Programmen bereits eingesetzt habe und so nur mit einer Art ID über einen CallForm(..) ein Formular erzeugen/Anzeigen konnte, incl. Übergabe von Parametern und Rückgabe von Werten.. |
AW: Problem mit Klassendesign
Zitat:
![]() ![]() |
AW: Problem mit Klassendesign
Zitat:
Gruß K-H |
AW: Problem mit Klassendesign
Genau, sonst könnte man sich das ganze Geraffel ja auch schenken.
|
AW: Problem mit Klassendesign
Zitat:
|
AW: Problem mit Klassendesign
Wenn Du einen besseren Vorschlag hast, immer her damit.
|
AW: Problem mit Klassendesign
@DeddyH
Vielen Dank für deine Demo! Mit der Demo wurde mir die konkrete Anwendung für die Factory erst klar, bisher wars eher theoretisch... :-D Hätte jemand eine Idee wie man das Demo von DeddyH unter Delphi 2007 zum Laufen bekommt, da die noch keine System.Generics.Collections und System.Rtti kennt? :gruebel: lg, jus |
AW: Problem mit Klassendesign
Zitat:
Mit und ohne Parameter:
Delphi-Quellcode:
Nix verlinkt, kein RTTI...
initialization
TAnyFactory.Default.RegisterObj<ICanHandleTiere,THund>('Hund'); TAnyFactory.Default.RegisterObj<ICanHandleTiere,TKatze>('Katze'); TAnyFactory.Default.RegisterObj<ICanHandleTiere>(Function : TSchwein begin Result := TSchwein.Create('ImDreck'); end,'Schwein'); TAnyFactory.Default.RegisterObj<ICanHandleTiere>(Function : TVogel begin Result := TVogel.Create(TAppGlobal.Factory.Get<boolean>('Kannfliegen')); // Externer Parameter end,'Vogel'); end. // Andere Unit... Uses MyInterfaces; // Kein Link zur Implementation var Hund,Katze,Schwein,Pinguin : ICanHandleTiere; begin Hund := TAnyFactory.Default.CreateObj<ICanhandleTiere>('Hund'); TAppGlobal.Factory.SetTo<Boolean>('Kannfliegen',false); Pinguin := TAnyFactory.Default.CreateObj<ICanHandleTiere>('Vogel'); // oder - Falls nicht klar ist, ob die Implementation schon enthalten ist if TAnyFactory.Default.TryCreateObj<ICanHandleTiere>(Katze,'Katze') then Katze.GibLaut; TAnyFactory.Default.CallIfExists<ICanHandleTiere>(Procedure ALöwe : ICanHandleTiere) begin ALöwe.GibLaut; end,'Löwe'); end; Grüsse Mavarik :coder: |
AW: Problem mit Klassendesign
Zitat:
![]() ![]() |
AW: Problem mit Klassendesign
ok, es scheint so zu sein, dass die TypeInfo nicht so viele Infos in Delphi2007 hergibt. Kann man eigentlich diese Entkopplung mit registerclass auch lösen? :gruebel:
lg, jus |
AW: Problem mit Klassendesign
Hallo,
mich fasziniert die Lösung von DeddyH. :thumb: Da ich in Delphi2007 den Code von DeddyH nicht kompilieren kann, habe ich mal folgendes mit Hilfe von Codeschnipsel aus dem Internet zusammengeschustert:
Delphi-Quellcode:
unit TierIntf;
interface type ITier = interface ['{9DC595F6-9026-4C4B-9FAC-5CCC5437C8A5}'] procedure GibLaut; end; implementation end.
Delphi-Quellcode:
unit TierFactory;
interface uses Classes,TierIntf; type TTier = class(TInterfacedPersistent, ITier) public class function CreateInstance(Name: string): TTier; overload; procedure GibLaut; virtual; abstract; end; implementation { TTier } class function TTier.CreateInstance(Name: string): TTier; var AClass: TPersistentClass; begin Result := nil; AClass := GetClass(Name); if Assigned(AClass) then begin Result := AClass.NewInstance as TTier; Result.Create; end else { error handle } end; end.
Delphi-Quellcode:
unit Hund;
interface uses Dialogs, Classes, TierFactory; type THund = class(TTier) public procedure GibLaut; override; end; implementation { THund } procedure THund.GibLaut; begin inherited; showmessage('Ich bin ein'+ Classname); end; initialization RegisterClass(THund); end.
Delphi-Quellcode:
Meine Frage an die Experten, ist der obige Code noch Factory konform, oder ist es nur irgendwas? :oops:
unit Unit1;
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private-Deklarationen } public { Public-Deklarationen } end; var Form1: TForm1; implementation uses TierFactory; {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var Tier: TTier; begin Tier := TTier.CreateInstance('THund'); if Assigned(Tier) then begin Tier.GibLaut; Tier.Free; end; end; end. Lg, jus |
AW: Problem mit Klassendesign
Zitat:
In diesem ähnlichen Code würde der Konstruktor nicht aufgerufen:
Delphi-Quellcode:
Dieses negative Resultat kann ich mir auch bei einem THund Konstruktor vorstellen.
type
TMyClass = class(TObject) MyStrings: TStrings; constructor Create; virtual; end; constructor TMyClass.Create; begin MyStrings := TStringList.Create; end; procedure Test; var Clazz: TClass; Instance: TObject; begin Clazz := TMyClass; Instance := Clazz.Create; end; |
AW: Problem mit Klassendesign
@mjustin hast recht, in diesem Fall wird es nicht aufgerufen. Ich muß zugeben, dass ich bisher nicht soviel mit Objekten gearbeitet habe. Wenn ich dann absichtlich die TTier Klasse um einen Konstruktor wie folgt erweitere, merke ich, dass dieser abgearbeitet wird. Wäre es dann so in Ordnung?
Delphi-Quellcode:
unit TierFactory;
interface uses Classes, TierIntf, Dialogs; type TTier = class(TInterfacedPersistent, ITier) public constructor Create; virtual; abstract; class function CreateInstance(Name: string): TTier; overload; procedure GibLaut; virtual; abstract; end; implementation { TTier } class function TTier.CreateInstance(Name: string): TTier; var AClass: TPersistentClass; begin Result := nil; AClass := GetClass(Name); if Assigned(AClass) then begin Result := AClass.NewInstance as TTier; Result.Create; end else { error handle } end; end.
Delphi-Quellcode:
unit Hund;
interface uses Dialogs, Classes, TierFactory; type THund = class(TTier) public constructor Create; override; procedure GibLaut; override; end; implementation { THund } constructor THund.Create; begin inherited; end; procedure THund.GibLaut; begin inherited; showmessage('Ich bin ein '+ Classname); end; initialization RegisterClass(THund); finalization UnRegisterClass(THund); end. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 05:11 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