Autor Beitrag
jasocul
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 6388
Erhaltene Danke: 146

Windows 7 + Windows 10
Sydney Prof + CE
BeitragVerfasst: Mi 04.06.14 08:26 
Blöder Titel, aber mir viel nichts besseres dazu ein. Wer einen Vorschlag hat, her damit.

Zum Thema:
Ich habe eine Klasse, die aus einer XML-Datei etwas einliest. Dies passiert an sehr vielen Stellen der Datei und die Struktur ist in verschiedenen Klassen aufgeteilt, damit ich im Source einfacher darauf zugreifen kann. Es ist jedes mal eine andere Struktur, die eingelesen werden muss.

Meine Klasse sieht zur Zeit so aus:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
interface
type
  TDataList = Class(TDataBase)
    Items : TList;
  public
    constructor Create(Node : IXMLNode);
    destructor Destroy; override;
    function GetItem(Index : Integer) : TDataListElement;
  End;

implementation
constructor TDataList.Create(Node: IXMLNode);
var
  nl : IXMLNodeList;
  cnt : Integer;
begin
  Items := TList.Create;
  nl := Node.ChildNodes;
  for cnt := 0 to nl.Count - 1 do
  begin
    Items.Add(TDataListElement.Create(nl[cnt]));
  end;
end;

destructor TDataList.Destroy;
begin
  Items.Free;
  inherited;
end;

function TDataList.GetItem(Index: Integer) : TDataListElement;
begin
  if (Index >= 0and (Index < Items.Count) then
  begin
    Result := TDataListElement(Items.Items[Index]);
  end
  else
  begin
    Result := nil;
  end;
end;


Das funktioniert auch, aber ich möchte es "vereinfachen".
Es gibt ganz viele dieser Listen und List-Elemente, die natürlich alle unterschiedliche Klassen sind.
Aber gerade die Listenklassen sind völlig identisch, bis auf die Angabe des List-Elements.

Habe ich ein Brett vor dem Kopf und komme nicht auf die richtige Idee oder gibt es wirklich keinen brauchbaren Ansatz, um das zu lösen?
Im Grunde müsste ich doch nur die Element-Klasse irgendwie zur Listen-Klasse "übergeben".
Einen Type-Cast innerhalb des Sources, der die Klassen verwendet, möchte ich dabei vermeiden.
Xion
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
EE-Maler
Beiträge: 1952
Erhaltene Danke: 128

Windows XP
Delphi (2005, SmartInspect), SQL, Lua, Java (Eclipse), C++ (Visual Studio 2010, Qt Creator), Python (Blender), Prolog (SWIProlog), Haskell (ghci)
BeitragVerfasst: Mi 04.06.14 17:35 
Klingt für mich total nach Generics (im Prinzip wie Pointer nur ohne Cast ;) ). Dabei legt dann der Compiler einfach für jeden Typ eine Klasse an, es bringt also nichts bzgl. Performanz, nur bzgl. Programmieraufwand.
Kann aber mein Delphi noch nicht, deswegen kann ich dir dazu nichts weiter sagen.

_________________
a broken heart is like a broken window - it'll never heal
In einem gut regierten Land ist Armut eine Schande, in einem schlecht regierten Reichtum. (Konfuzius)

Für diesen Beitrag haben gedankt: jasocul
GuaAck
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 378
Erhaltene Danke: 32

Windows 8.1
Delphi 10.4 Comm. Edition
BeitragVerfasst: Mi 04.06.14 19:30 
Hallo Jasocul,

so ganz verstanden habe ich es nicht. Sieht aber nach Polymorphie aus: Du machst eine Klasse TallgemeineListe, da kommt das hinein, was für alle Listen gleich ist.
Dann macht Du deine speziellen Listen als class(TallgemeineListe).

Wenn Du dann eine Variable "zubearbeitenListe : TallgemeineListe" definierst, dann kannst Du dieser per Create beliebige der abgeleiteten Klassen zuweisen. Dann hängt der Code, der dann mit der Variablen "zubearbeitendeListe" was macht, nicht davon ab, von welchem Typ genau die Klasse ist.

Anbei als Anhang ein kleines Testprogramm, mit dem ich die Sache vor einiger zeit einmal geübt habe, vielleicht hilft es ja.

Gruß
GuaAck
Einloggen, um Attachments anzusehen!

Für diesen Beitrag haben gedankt: jasocul
jasocul Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 6388
Erhaltene Danke: 146

Windows 7 + Windows 10
Sydney Prof + CE
BeitragVerfasst: Do 05.06.14 08:16 
@user profile iconXionMit Generics hatte ich schon einen Ansatz versucht, aber bin daran gescheitert. Habe aber in dem Bereich auch noch nicht so viel Erfahrung. In einem anderen Projekt habe ich Generics auch schonmal umgesetzt. Aber hier bekomme ich es nicht hin. Den Versuch hatte ich dann komplett verworfen (Source nicht mehr verfügbar) und dann hier diesen Thread erstellt. Vielleicht sollte ich es nochmal versuchen, wenn mein Kopf wieder frei dafür ist. Oder Jemand erbarmt sich meiner und zeigt mir, wie es richtig gemacht wird. :wink:

@user profile iconGuaAckDarüber hatte ich auch kurz nachgedacht, aber verworfen. Der Teil, der sich vererben lässt, ist minimal und bringt daher kaum etwas. Aber ich gebe zu, dass es sauberer vom Stil wäre.
GuaAck
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 378
Erhaltene Danke: 32

Windows 8.1
Delphi 10.4 Comm. Edition
BeitragVerfasst: Fr 06.06.14 00:02 
Hallo Jasocul,

meine Anregung betraf nicht nur Vererbung, es geht einen Schritt weiter: Z.B. Ein Programm, dass auf einer Rennstrecke fahrende Autos simuliert:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
Procedure Rennstrecke;
...
auto.bremse;
auto.lenke;
auto.beschleunige;
...
end;


Wenn man auto als T_auto deklariert und die Klassen T_auto_LKW und T_auto_Sportwagen von T_auto abgeleitet sind, dann funktioniert die Procedure sowohl mit auto := T_Auto_LKW.create als auch mit ...Sportwagen.create. (Virtual usw. muss hier und da dabei sein.) Bremse, Lenke und Beschleunige sind ja individuell für die Fahrzeugtypen, deshalb sind das keine geerbten Dinge.

Wenn man nun auch noch einen Ferrari simulieren möchte, dann braucht die Prodedure Rennstrecke nicht erweitert werden. Man muss nur auto := T_auto_Ferrari deklarieren und naturlich bremse/lenke/beschleunige für den Ferrari erstellen.

Ist es nicht das was Du für Deine Listen brauchst?

Wie gesagt, so ganz verstanden habe ich Dein Problem nicht,

Gruß GuaAck

P.S.: Ich sehe ein merkwürdige Formatierung im Delphi-Bereich in der Vorschau. Hoffentlich ist es gleich korrekt.
jasocul Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 6388
Erhaltene Danke: 146

Windows 7 + Windows 10
Sydney Prof + CE
BeitragVerfasst: Fr 06.06.14 08:10 
Hallo GuaAck,

damit wäre ich nicht viel weiter, als jetzt.

Im Prinzip ist es ganz einfach. Meine Listenklasse macht im Prinzip immer dasselbe. Nur der Typ der Elemente ist bei jeder Instanz anders. Außer diese Elemente einzutragen und diese zu auszulesen, muss die Listenklasse nichts damit machen.
Die Abweichungen in den Methoden der Listen-Klasse sind nur im Constructor und in GetItem. Und da weicht auch nur der Typ der Elemente je Instanz ab. Zur Zeit muss ich für jeden Element-Typ eine eigene Listen-Klasse erstellen, nur weil der Element-Typ anders ist.

Auch mit deinem Ansatz müsste ich jedesmal den Constructor und GetItem neu schreiben. Es bliebe nur Destroy und die TList Variabel Items gleich. Damit hätte ich fast nichts gewonnen. Es wäre sicher richtig hier OOP konsequent anzuwenden, aber ich suche halt noch nach einer Alternative, um mir genau diesen Aufwand zu sparen.

Ich denke, dass Generics der richtige Ansatz sind, aber ich komme wohl erst nächste Woche dazu, das nochmal damit zu versuchen.
guinnes
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 182
Erhaltene Danke: 14



BeitragVerfasst: Fr 06.06.14 11:25 
Die Typen, die du zurückgeben mußt sind dir ja sicherlich alle bekannt. Du könntest ( ähnlich wie bei TField ) properties machen, die den gewünschten Typ zurückgeben. Dadurch hättest du nur eine Stelle, wo du Casten mußt :
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
Property ItemAsTFoo[Index : Integer] : TFoo Read GetItemAsTFoo;
Property ItemAsTBar[Index : Integer] : TBar Read GetItemAsTBar;

function TBluberblaber.ItemAsTFoo(Index : Integer) : TFoo;
begin
  Result := Item[Index] as TFoo;
end;

Für diesen Beitrag haben gedankt: jasocul, Narses
WasWeißDennIch
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 653
Erhaltene Danke: 160



BeitragVerfasst: Fr 06.06.14 12:12 
Ich habe das Problem auch nicht richtig verstanden, aber wäre auch so etwas denkbar?
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
type
  TTier = class
  public
    procedure GibLaut; virtualabstract;
  end;

  THund = class(TTier)
  public
    procedure GibLaut; override;
  end;

  TKatze = class(TTier)
  public
    procedure GibLaut; override;
  end;

  TTierClass = class of TTier;

  TTiere = class
  private
    FList: TObjectList;
    FItemType: TTierClass;
    function GetCount: integer;
    function GetItem(Index: integer): TTier;
  public
    constructor Create(ACount: integer; TierType: TTierClass);
    destructor Destroy; override;
    property Count: integer read GetCount;
    property Item[Index: integer]: TTier read GetItem; default;
    property ItemType: TTierClass read FItemType;
  end;

...

{ THund }

procedure THund.GibLaut;
begin
  ShowMessage('Wuff');
end;

{ TKatze }

procedure TKatze.GibLaut;
begin
  ShowMessage('Miau');
end;

{ TTiere }

constructor TTiere.Create(ACount: integer; TierType: TTierClass);
var
  i: integer;
begin
  inherited Create;
  FList := TObjectList.Create;
  FItemType := TierType;
  for i := 1 to ACount do
    FList.Add(TierType.Create);
end;

destructor TTiere.Destroy;
begin
  FList.Free;
  inherited;
end;

function TTiere.GetCount: integer;
begin
  Result := FList.Count;
end;

function TTiere.GetItem(Index: integer): TTier;
begin
  Result := FList[Index] as FItemType;
end;


Somit könnte man den zu erzeugenden Typ gleich mit angeben, z.B. 3 Hunde:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
procedure TFormTest.btnHundeClick(Sender: TObject);
var
  Tiere: TTiere;
  i: integer;
begin
  Tiere := TTiere.Create(3, THund);
  try
    for i := 0 to Tiere.Count - 1 do
       TTier(Tiere[i]).GibLaut;
  finally
    Tiere.Free;
  end;
end;

oder 5 Katzen:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
procedure TFormTest.btnKatzenClick(Sender: TObject);
var
  Tiere: TTiere;
  i: integer;
begin
  Tiere := TTiere.Create(5, TKatze);
  try
    for i := 0 to Tiere.Count - 1 do
       TTier(Tiere[i]).GibLaut;
  finally
    Tiere.Free;
  end;
end;

Für diesen Beitrag haben gedankt: jasocul, Narses, Xion
jasocul Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 6388
Erhaltene Danke: 146

Windows 7 + Windows 10
Sydney Prof + CE
BeitragVerfasst: Di 10.06.14 08:00 
Danke user profile iconguinnes und user profile iconWasWeißDennIch.
Die Ansätze sind interessant und könnten mein "Problem" vermutlich auch lösen. Jetzt muss ich nur noch sehen, wann ich diese Woche dazu komme, das auszuprobieren.

Ich betrachte das Thema jetzt erstmal als beantwortet.
Danke für eure Ideen und Hilfe.
jaenicke
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 19284
Erhaltene Danke: 1742

W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
BeitragVerfasst: Di 10.06.14 16:55 
Der Grund weshalb Generics nicht direkt funktionieren ist doch, dass du die Datenklasse nicht erstellen kannst ohne zu wissen wie deren Konstruktor aussieht. Dafür gibt es verschiedene Lösungen. Die einfachste wäre, wenn alle Klassen von einer Basisklasse abgeleitet wären. Dann kannst du einen Constraint auf diese Klasse setzen. Wenn das nicht geht, ist eine einfach Variante eine anonyme Funktion zur Erzeugung an den Konstruktor mitzugeben, was aber bei der Verwendung mehr Aufwand bedeutet:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
type
  TDataList<T: class> = class(TDataBase)
  private
    type
      TOnCreateElement = reference to function(const ANode: IXMLNode): T;
    var
      FItems: TList<T>;
    function GetItem(Index: Integer): T;
  public
    constructor Create(const AOnCreateElement: TOnCreateElement; const ANode: IXMLNode);
    destructor Destroy; override;
    property Item[Index : Integer]: T read GetItem;
  end;

implementation

{ TDataList<T> }

constructor TDataList<T>.Create(const AOnCreateElement: TOnCreateElement; const ANode: IXMLNode);
var
  nl : IXMLNodeList;
  cnt : Integer;
begin
  FItems := TList<T>.Create;
  nl := ANode.ChildNodes;
  for cnt := 0 to nl.Count - 1 do
  begin
    FItems.Add(AOnCreateElement(nl[cnt]));
  end;
end;

destructor TDataList<T>.Destroy;
begin
  FItems.Free;
  inherited;
end;

function TDataList<T>.GetItem(Index: Integer): T;
begin
  if (Index >= 0and (Index < FItems.Count) then
    Result := FItems.Items[Index]
  else
    Result := nil;
end;