Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Delphi Problem mit Klassendesign (https://www.delphipraxis.net/191593-problem-mit-klassendesign.html)

jus 31. Jan 2017 14:43

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:
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;
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?

lg,
jus

mkinzler 31. Jan 2017 14:48

AW: Problem mit Klassendesign
 
Genau so.

CarlAshnikov 31. Jan 2017 14:59

AW: Problem mit Klassendesign
 
Sieht doch schon gut aus. Es fehlen nur
Delphi-Quellcode:
virtual
und
Delphi-Quellcode:
override
an den entsprechenden Methoden, damit auch die abgeleiteten Versionen benutzt werden.

haentschman 31. Jan 2017 15:02

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:

TBx 31. Jan 2017 15:06

AW: Problem mit Klassendesign
 
Die Deklaration deiner
Delphi-Quellcode:
procedure
sollte so ok sein.
Nur Deine Klassen solltest Du noch anpassen, in der Elternklasse sollten die Methoden
Delphi-Quellcode:
virtual
und evtl. auch
Delphi-Quellcode:
abstract
sein.
Eine Erklährung findest Du hier und hier.

samso 31. Jan 2017 15:10

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;

Noocode 31. Jan 2017 16:54

AW: Problem mit Klassendesign
 
Ich persönlich möchte als weiteren Vorschlag das Entwurfsmuster Factory einbringen.

p80286 1. Feb 2017 12:27

AW: Problem mit Klassendesign
 
Zitat:

Zitat von haentschman (Beitrag 1360467)
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:

das hört sich interessant an, aber wie macht man das in der Praxis?

Gruß
K-H

TBx 1. Feb 2017 12:37

AW: Problem mit Klassendesign
 
Zitat:

Zitat von p80286 (Beitrag 1360545)
das hört sich interessant an, aber wie macht man das in der Praxis?

Das sieht in der Deklaration ähnlich aus. Man hat nur keine Vererbung und kann auch ganz andere Objekte verwenden, solange diese nur das Interface IDatabase implementieren:
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;

jus 2. Feb 2017 23:08

AW: Problem mit Klassendesign
 
Zitat:

Zitat von samso (Beitrag 1360471)
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;

Das Beispiel von samso hat schon mal auf Anhieb funktioniert. Vielen Dank für Eure Mühen! :thumb: Das mit den Interfaces muß ich mir in Ruhe mal anschauen.

Lg,
jus

p80286 3. Feb 2017 07:56

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

Aviator 3. Feb 2017 08:36

AW: Problem mit Klassendesign
 
Zitat:

Zitat von p80286 (Beitrag 1360678)
@TBx
Danke! Ich hatte ein eigenes Interface für jede DB erwartet. Werde mir das mal bei Gelegenheit anschauen.

Gruß
K-H

Das würde ja gerade den Sinn von Interfaces zerstören. Hier geht es ja gerade darum, dass du immer mit dem gleichen Interface und den gleichen Methoden arbeitest. Lediglich bei der Erstellung der Interface Instanz wird dann entweder TMySql oder TMySqlPhpTunnel erstellt. Das wiederum kann dann später eine Factory erledigen. Wobei ich das auch noch nicht gemacht habe und da bei mir auch noch ein paar Verständnisprobleme vorliegen.

DeddyH 3. Feb 2017 11:15

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:
unit TierIntf;

interface

type
  ITier = interface
    ['{9DC595F6-9026-4C4B-9FAC-5CCC5437C8A5}']
    procedure GibLaut;
  end;

implementation

end.
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.
Delphi-Quellcode:
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.
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.
Delphi-Quellcode:
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.
Für weitere Tierarten kann man diese Unit einfach kopieren und alle Vorkommen von "Hund" einfach durch "Katze", "Vogel", "Fisch" oder sonstwas ersetzen.
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:
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.
Das war alles, wer grobe Fehler finden sollte, bitte korrigieren.

Aviator 3. Feb 2017 12:33

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.

DeddyH 3. Feb 2017 12:51

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.

HolgerX 3. Feb 2017 15:07

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..

freimatz 3. Feb 2017 15:44

AW: Problem mit Klassendesign
 
Zitat:

Zitat von HolgerX (Beitrag 1360768)
Jetzt weiß ich endlich, was Ihr immer mit einer Factory meint ;)

https://de.wikipedia.org/wiki/Abstrakte_Fabrik
https://de.wikipedia.org/wiki/Fabrikmethode

p80286 3. Feb 2017 16:14

AW: Problem mit Klassendesign
 
Zitat:

Zitat von DeddyH (Beitrag 1360748)
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.

Das ist ja wohl das grundsätzliche Bestreben, selbst wenn man z.Zt. mit z.B. mehreren Datenmodulen hantiert.

Gruß
K-H

DeddyH 3. Feb 2017 16:50

AW: Problem mit Klassendesign
 
Genau, sonst könnte man sich das ganze Geraffel ja auch schenken.

idefix2 5. Feb 2017 12:27

AW: Problem mit Klassendesign
 
Zitat:

Zitat von DeddyH (Beitrag 1360719)
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.

An der Stelle hakt meines Erachtens die Praxistauglichkeit des Konzepts. Wenn ein Create Parameter annimmt, dann im allgemeinen nicht aus Jux und Tollerei, sondern weil die Parameterwerte für irgend etwas gebraucht werden. In der Factory einfach Nullwerte für die Parameter anzunehmen, wird in aller Regel keine sinnvollen Ergebnisse bringen, oder irre ich mich da? Und wenn das Hauptprogramm keinerlei Kenntnis von der zu erstellenden Klasse hat, wird es schwer adäquate Parameter bereitstellen können.

DeddyH 5. Feb 2017 12:50

AW: Problem mit Klassendesign
 
Wenn Du einen besseren Vorschlag hast, immer her damit.

jus 6. Feb 2017 10:14

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

Mavarik 6. Feb 2017 11:21

AW: Problem mit Klassendesign
 
Zitat:

Zitat von DeddyH (Beitrag 1360844)
Wenn Du einen besseren Vorschlag hast, immer her damit.

Ich mache es so:

Mit und ohne Parameter:

Delphi-Quellcode:
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;
Nix verlinkt, kein RTTI...

Grüsse

Mavarik :coder:

Stevie 6. Feb 2017 14:07

AW: Problem mit Klassendesign
 
Zitat:

Zitat von Mavarik (Beitrag 1360890)
Zitat:

Zitat von DeddyH (Beitrag 1360844)
Wenn Du einen besseren Vorschlag hast, immer her damit.

Ich mache es so:

Gruselig, weil (siehe auch https://youtu.be/RlfLCWKxHJ0?t=8m43s)

jus 8. Feb 2017 10:45

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

jus 8. Feb 2017 13:46

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:
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.
Meine Frage an die Experten, ist der obige Code noch Factory konform, oder ist es nur irgendwas? :oops:

Lg,
jus

mjustin 8. Feb 2017 14:53

AW: Problem mit Klassendesign
 
Zitat:

Zitat von jus (Beitrag 1361109)
Meine Frage an die Experten, ist der obige Code noch Factory konform, oder ist es nur irgendwas? :oops:

Sieht interessant aus - spannend ist zu klären, ob ein Konstruktor aufgerufen würde. Angenommen, THund hat einen parameterlosen Konstruktor. Wird er aufgerufen?

In diesem ähnlichen Code würde der Konstruktor nicht aufgerufen:

Delphi-Quellcode:
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;
Dieses negative Resultat kann ich mir auch bei einem THund Konstruktor vorstellen.

jus 8. Feb 2017 22:46

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