Autor |
Beitrag |
jasocul
Beiträge: 6388
Erhaltene Danke: 146
Windows 7 + Windows 10
Sydney Prof + CE
|
Verfasst: 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:
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 >= 0) and (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
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)
|
Verfasst: 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
Beiträge: 378
Erhaltene Danke: 32
Windows 8.1
Delphi 10.4 Comm. Edition
|
Verfasst: 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
Beiträge: 6388
Erhaltene Danke: 146
Windows 7 + Windows 10
Sydney Prof + CE
|
Verfasst: Do 05.06.14 08:16
@ XionMit 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.
@ GuaAckDarü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
Beiträge: 378
Erhaltene Danke: 32
Windows 8.1
Delphi 10.4 Comm. Edition
|
Verfasst: 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:
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
Beiträge: 6388
Erhaltene Danke: 146
Windows 7 + Windows 10
Sydney Prof + CE
|
Verfasst: 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
Beiträge: 182
Erhaltene Danke: 14
|
Verfasst: 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 :
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
Beiträge: 653
Erhaltene Danke: 160
|
Verfasst: Fr 06.06.14 12:12
Ich habe das Problem auch nicht richtig verstanden, aber wäre auch so etwas denkbar?
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; virtual; abstract; 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;
...
procedure THund.GibLaut; begin ShowMessage('Wuff'); end;
procedure TKatze.GibLaut; begin ShowMessage('Miau'); end;
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:
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:
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
Beiträge: 6388
Erhaltene Danke: 146
Windows 7 + Windows 10
Sydney Prof + CE
|
Verfasst: Di 10.06.14 08:00
Danke guinnes und WasWeiß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
Beiträge: 19284
Erhaltene Danke: 1742
W11 x64 (Chrome, Edge)
Delphi 11 Pro, Oxygene, C# (VS 2022), JS/HTML, Java (NB), PHP, Lazarus
|
Verfasst: 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: 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
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 >= 0) and (Index < FItems.Count) then Result := FItems.Items[Index] else Result := nil; end; |
|
|
|