![]() |
Verwalten von Objekten in einer Container-Klasse
Liste der Anhänge anzeigen (Anzahl: 2)
Verwalten von Objekten in einer Container-Klasse
Abstract Wie man OOP konform Objekte in einer Container-Klasse verwaltet. Problemstellung Oftmals hat man das Problem, dass man mehrere gleichartige Elemente verwalten muss - zum Beispiel Adressen in einem Adressbuch oder Spieler eines Spieles. Meist wird dann ein Record genommen und diese Records werden dann in einem dynamische Array gespeichert. Dies ist zum einem etwas umständlich und mit einem gewissen Aufwand verbunden und zum dem ist es nicht OOP konform. Lösungsmöglichkeit Um dies Problem OOP konform zu lösen, arbeitet man mit zwei Klassen: Einer Container-Klasse und einer Klasse für die zu verwaltenden Objekte. Anstatt eines Records wird also eine Klasse benutzt und die Objekte dieser Klasse werden nicht in einem dynamischen Array abgelegt, sondern in einer Container-Klasse. Programmiert man in Delphi, werden einem schon verschiedene Klassen angeboten, die man als Container-Klasse verwenden kann: TList, TObjectList und TCollection.
Die Container-Klasse kapselt eine Liste vom Typ TList. (Man könnte auch eine Liste vom Typ TObjectList nehmen, was in der Praxis wohl auch sinnvoller wäre, da man dann den Speicher nicht selber verwalten muss. Ich habe mich hier aber für die Klasse TList entschieden, um zu zeigen, wie man den Speicher selber verwalten müsste in diesem Fall.) Da man die Liste kapselt, kann man selber bestimmen, welche Methoden mit welchen Parametern sichtbar sein sollen. Somit kann man dann auch sicherstellen, dass nur Objekte einer bestimmten Klasse in der Liste aufgenommen werden können. Wir haben uns somit eine streng typisierte Liste geschaffen. Eine Container-Klasse könnte dann zum Beispiel so aussehen:
Delphi-Quellcode:
Die Methoden der typisierte Liste
TContactList = class(TObject)
private FInnerList: TList; function GetItem(Index: Integer): TContact; procedure SetItem(Index: Integer; Contact: TContact); function GetCount: Integer; public constructor Create; destructor Destroy; override; procedure Add(Contact: TContact); procedure Delete(Index: Integer); property Count: Integer read GetCount; property Items[Index: Integer]: TContact read GetItem write SetItem; end; Gucken wir uns exemplarisch die Methode Add unserer List an:
Delphi-Quellcode:
Unsere eigene Methode Add ruft also im Prinzip nur die Methode Add von unser inneren Liste FInnerList (die natürlich im Konstruktor erzeugt und im Destruktor wieder freigegeben werden muss) auf. Da man allerdings nur ein Objekt der Klasse TContact übergeben kann, kann man nur Objekte diesen Typs in die Liste aufnehmen.
procedure TContactList.Add(Contact: TContact);
begin FInnerList.Add(Contact); end; Den Speicher verwalten Wie schon gesagt muss man den Speicher selber verwalten, wenn man keine Liste vom Typ TObjectList nimmt. Da man in der Liste eine Instanz einer Klasse ablegt, die Speicher belegt, muss man diesen Speicher auch wieder freigeben, wenn man ein Objekt aus der Liste entfernt oder, wenn man die ganze Liste wieder freigibt. Löscht man einen Eintrag der Liste, sähe dies dann so aus:
Delphi-Quellcode:
Erst wird das Objekt in der Liste freigegeben und dann aus der selbigen gelöscht. Ebenso verfährt man beim Freigeben der Container-Klasse:
procedure TContactList.Delete(Index: Integer);
begin // destroy object TObject(FInnerList.Items[Index]).Free; // delete object from the list FinnerList.Delete(Index); end;
Delphi-Quellcode:
Erst geht man die Liste durch und gibt alle in ihr enthaltenen Objekte frei. Dann gibt man die Liste selber frei. Wichtig ist, dass die Schleife rückwärts laufen muss, da die Eingangsbedingung einer for-Schleife nur beim Eintritt in die Schleife geprüft wird, aber in der Schleife entfernen wir ja Elemente, so dass wir, wenn die Schleife vorwärts liefe, über die Anzahl der Elemente hinauslaufen würden.
destructor TContactList.Destroy;
var i : Integer; begin if FInnerList.Count > 0 then begin for i := FInnerList.Count - 1 downto 0 do begin TObject(FInnerList.Items[i]).Free; end; end; FInnerList.Free; inherited; end; Auslesen der Liste Das Auslesen der Objekte ist dann eher trivial:
Delphi-Quellcode:
Wie man sieht wurde unsere Container-Klasse noch mal in einer weiteren Klasse TAddressBook gekapselt:
procedure TfrmMain.UpdateListBox;
var Contact : TContact; i : Integer; s : string; begin ListBox1.Items.Clear; for i := 0 to AddressBook.Contacts.Count - 1 do begin Contact := AddressBook.Contacts.Items[i]; s := Contact.LastName + ', ' + Contact.FirstName; ListBox1.Items.Add(s); end; end;
Delphi-Quellcode:
Dies dient nur dazu die Aufgaben der Klassen sauber zu trennen. Die Klasse TContactList dient nur dazu die Liste der Kontakte zu verwalten. Die Klasse TAddressBook hingegen nimmt später dann noch alle weiteren Methoden unseres Adressbuches auf.
TAddressBook = class(TObject)
private FContacts: TContactList; public constructor Create; destructor destroy; override; property Contacts: TContactList read FContacts; end; Im Anhang das Demo-Projekt. Edit: Vorschläge eingearbeitet. |
Re: Verwalten von Objekten in einer Container-Klasse
Ich habe das ganze noch mal als PDF angehangen und das Tutorial steht jetzt auch als Artikel auf meiner Homepoage zur Verfügung:
![]() |
Re: Verwalten von Objekten in einer Container-Klasse
Es gibt zwei Arten von Wiederverwendung in der OOP: Vererbung und Komposition.
Bei der Vererbung hat der neue Typ alle "Features" (Eifel-speak ;) ) des Vorgängers. Bei der Komposition hält man eine Instanz eines anderen Types als Feld und verwendet diese Instanz. Somit hat man eine bessere Kontrolle über das, was von außen verwendet werden kann. Eine streng typisierte Liste durch Erben von TList ist ein Ding der Unmöglichkeit. Du hast immer noch die Methode Add, welche einen Pointer nimmt und man kann immer noch einen Pointer auf TIrgendwas reinwerfen. In deinem Beispiel wäre es also besser ein Feld vom Typen TObjectList herzunehmen, welches deine Daten hält. Aber nach außen bit du wirklich streng auf deine Elementenklasse typisiert. Außerdem klingt eine Klasse namens "AddressBook" wie etwas, was mehr als nur ein paar Adressen enthält. Eher wie eine weitere Fachklasse, in der ein öhm Adressbuch modelliert ist. Bleistift:
Delphi-Quellcode:
TContactList = class
private fInnerList : TObjectList; function GetItem(aIndex: Integer): TContact; procedure SetItem(aIndex: Integer; aItem: TContact); protected property InnerList : TObjectList read fInnerList; public destructor Destroy; override; procedure Add(aItem : TContact); procedure Delete(aIndex: Integer); property Items[aIndex: Integer]: TContact read GetItem write SetItem; end; TAddressBook = class ... public property Contacts : TContactList read fContacts; ... |
Re: Verwalten von Objekten in einer Container-Klasse
Deine Klassenaufteilung ist natürlich besser, jezt wo ich das so sehe. ;)
|
Re: Verwalten von Objekten in einer Container-Klasse
Zitat:
|
Re: Verwalten von Objekten in einer Container-Klasse
Zitat:
|
Re: Verwalten von Objekten in einer Container-Klasse
@Ingo:
Der Einwand ist sachlich korrekt, ich denke jedoch, dass es unserem King of Volksmusik mehr um das Prinzip ging. @Sven: Der Unterschied wird genau dann deutlich, wenn diese Klasse von außen genutzt wird. Die einzigen Schnittstellen zu dieser Liste gehen über die Getter und Setter, die für die Typensicherheit sorgen. Nur dadurch, dass die Liste selbst nicht nach außen hin sichtbar ist und damit die Nutzung von deren Add-Methode nicht möglich ist, ergibt die Typ-Sicherheit. |
Re: Verwalten von Objekten in einer Container-Klasse
Zitat:
Zitat:
Wenn du von einer generischen Containerklasse erbst, könnte man doch immer noch (unbeabsichtigt) die untypisierten Methoden dieses Vorgängers benutzen. [1] Déja vu beabsichtigt, verwunderte Leser müssen also keinen Neurologen aufsuchen. :zwinker: |
Re: Verwalten von Objekten in einer Container-Klasse
Ich habe es jetzt so:
Delphi-Quellcode:
Aber hier:
type
TContact = class(TObject) private FFirstName: String; FLastName: String; function GetFirstName: String; procedure SetFirstName(const Value: String); function GetLastName: String; procedure SetLastName(const Value: String); public property FirstName: String read GetFirstName write SetFirstName; property LastName: String read GetLastName write SetLastName; end; TContactList = class(TObject) private FInnerList: TList; function GetItem(Index: Integer): TContact; procedure SetItem(Index: Integer; Contact: TContact); function GetCount: Integer; public constructor Create; destructor Destroy; override; procedure Add(Contact: TContact); procedure Delete(Index: Integer); property Count: Integer read GetCount; property Items[Index: Integer]: TContact read GetItem write SetItem; end; TAddressBook = class(TObject) private FContacts: TContactList; public constructor Create; destructor destroy; override; property Contacts: TContactList read FContacts write FContacts; end;
Delphi-Quellcode:
sagt er mir in Zeile 4:
procedure TContactList.Delete(Index: Integer);
begin // destroy object FInnerList.Items[Index].Free; FInnerList.Items[Index] := nil; // delete object from the list FInnerList.Delete(Index); end; Zitat:
|
Re: Verwalten von Objekten in einer Container-Klasse
Zitat:
Mach ein Typecasting
Delphi-Quellcode:
TObject(FInnerList.Items[Index]).Free;
Gerd |
Alle Zeitangaben in WEZ +1. Es ist jetzt 21:50 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 by Thomas Breitkreuz