AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Verwalten von Objekten in einer Container-Klasse
Tutorial durchsuchen
Ansicht
Themen-Optionen

Verwalten von Objekten in einer Container-Klasse

Ein Tutorial von Luckie · begonnen am 12. Mär 2007 · letzter Beitrag vom 17. Apr 2007
Antwort Antwort
Seite 2 von 4     12 34      
Benutzerbild von Luckie
Luckie
Registriert seit: 29. Mai 2002
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.
  • TList ist eine einfache Liste, in der beliebige Pointer abgelegt werden können. Die Klasse bringt schon Methoden zum Hinzufügen, Löschen usw. mit.
  • TObjectList ist eine Erweiterung der TList-Klasse und besonders für Objekte geeignet, da sie auch den Speicherplatz der Objekte selber verwalten kann.
  • TCollection ist eine spezielle Klasse, um Objekte vom Typ TCollectionItem zu verwalten. (Näheres dazu in der Delphi-Hilfe.)
Die Container-Klasse
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:
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;
Die Methoden der typisierte Liste
Gucken wir uns exemplarisch die Methode Add unserer List an:
Delphi-Quellcode:
procedure TContactList.Add(Contact: TContact);
begin
  FInnerList.Add(Contact);
end;
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.

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:
procedure TContactList.Delete(Index: Integer);
begin
  // destroy object
  TObject(FInnerList.Items[Index]).Free;
  // delete object from the list
  FinnerList.Delete(Index);
end;
Erst wird das Objekt in der Liste freigegeben und dann aus der selbigen gelöscht. Ebenso verfährt man beim Freigeben der Container-Klasse:
Delphi-Quellcode:
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;
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.

Auslesen der Liste
Das Auslesen der Objekte ist dann eher trivial:
Delphi-Quellcode:
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;
Wie man sieht wurde unsere Container-Klasse noch mal in einer weiteren Klasse TAddressBook gekapselt:
Delphi-Quellcode:
TAddressBook = class(TObject)
private
   FContacts: TContactList;
public
  constructor Create;
  destructor destroy; override;
  property Contacts: TContactList read FContacts;
end;
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.

Im Anhang das Demo-Projekt.

Edit: Vorschläge eingearbeitet.
Angehängte Dateien
Dateityp: zip adressbuch_538.zip (204,1 KB, 106x aufgerufen)
Dateityp: pdf container_klassen_482.pdf (66,2 KB, 134x aufgerufen)
Ein Teil meines Codes würde euch verunsichern.
 
Benutzerbild von Luckie
Luckie

 
Delphi 2006 Professional
 
#11
  Alt 13. Mär 2007, 17:03
Auf die Idee bin ich natürlich nicht gekommen. Dank dir. Ich werde dann heute Abend mal den Artikel und das Tuorial hier überarbeiten und wieder einstellen.
Michael
  Mit Zitat antworten Zitat
Benutzerbild von Khabarakh
Khabarakh
 
#12
  Alt 13. Mär 2007, 17:51
Abgesehen von Deriving or Wrapping hat sich ein ganz allgemeiner Fehler eingeschlichen:

Zitat von Luckie:
Delphi-Quellcode:
  if Assigned(Contact) then
    // ...
  else
    raise Exception.Create(rsObjectIsNil);

//...

  Self.Items[Index].Free;
  Self.Items[Index] := nil; // Hmm ;)
Sebastian
  Mit Zitat antworten Zitat
IngoD7

 
Delphi 7 Enterprise
 
#13
  Alt 13. Mär 2007, 20:41
Zitat von Luckie:
Ich habe es jetzt so:
Delphi-Quellcode:
(...)
  TAddressBook = class(TObject)
  private
     FContacts: TContactList;
  public
    constructor Create;
    destructor destroy; override;
    property Contacts: TContactList read FContacts write FContacts; //<------- write auch?? (IngoD7)
  end;
Frage:
Bist du sicher, dass du da die Referenzierung auf eine andere TContactList-Instanz ermöglichen möchtest?
Die Daten der im Konstruktor erzeugten Instanz von TContactList liegen danach im Nirvana.
  Mit Zitat antworten Zitat
Benutzerbild von Luckie
Luckie

 
Delphi 2006 Professional
 
#14
  Alt 13. Mär 2007, 22:47
Ups, war pure Gewohnheit.

@Khabarakh: Ich setze dort nur den Zeiger auf nil. Ist zwar an dieser Stelle wohl überflüssig, aber schaden sollte es nicht.
Michael
  Mit Zitat antworten Zitat
Benutzerbild von Khabarakh
Khabarakh
 
#15
  Alt 14. Mär 2007, 16:50
Generell schadet das Nillen nicht, aber wenn du im dadurch aufgerufenen Setter bei nil eine Exceptions wirfst, dann schadet es .
Sebastian
  Mit Zitat antworten Zitat
Benutzerbild von Luckie
Luckie

 
Delphi 2006 Professional
 
#16
  Alt 14. Mär 2007, 16:52
Delphi-Quellcode:
procedure TContactList.SetItem(Index: Integer; Contact: TContact);
begin
  if Assigned(Contact) then
    FInnerList.Items[Index] := Contact
end;
Michael
  Mit Zitat antworten Zitat
Thorben_K

 
Delphi 2006 Professional
 
#17
  Alt 14. Mär 2007, 19:15
Ich hoffe du erlaubst mir die Frage, aber der Sinn von TAdressBock ist mir nicht klar.

Willst du damit nur verhindern das jemand die ContactList verändern kann, oder wie seh ich das?
Thorben
  Mit Zitat antworten Zitat
Elvis

 
Delphi 2010 Professional
 
#18
  Alt 14. Mär 2007, 19:31
Zitat von Khabarakh:
Generell schadet das Nillen nicht, aber wenn du im dadurch aufgerufenen Setter bei nil eine Exceptions wirfst, dann schadet es .
Erst wenn du nichts mehr aus deinem Code entfernen kannst bist du fertig damit.
Sinnloses nil-en von Werten, die eh aus dem Scope rennen (weil das Element nicht mehr in der Liste ist!), halte ich für mehr als überflüssig.
Außerdem kannst du eine ObjectList mit OwnsObjects = true benutzen, und du musst dich damit nicht mehr befassen.


btw: Vielleicht sollte Michael den Thread mal auseinanderreisen: In das Tutorial und unser Fachchinesisch als "Diskussion zu ..."
Robert Giesecke
  Mit Zitat antworten Zitat
IngoD7

 
Delphi 7 Enterprise
 
#19
  Alt 14. Mär 2007, 20:05
Zitat von Thorben_K:
Ich hoffe du erlaubst mir die Frage, aber der Sinn von TAdressBock ist mir nicht klar.

Willst du damit nur verhindern das jemand die ContactList verändern kann, oder wie seh ich das?
So, wie TAdressBook da jetzt steht (also nur mit der Eigenschaft Contacts), hat es augenscheinlich keinen besonderen Nutzen. Da könnte man auch gleich eine TContactList instanziieren und verwenden.

Aber vielleicht spielt dieses Adressbuch ja beim Aufschlagen (programmtechnisch: im Konstruktor) ein Liedchen ab. Wer weiß ...
Ein Adressbuch hat ja (später) vielleicht auch noch mehr Eigenschaften, wie z.B. Notizseiten und Registerblätter, etc. pp.

Dass also TAdressBook hier überhaupt - und noch so nackt - steht, dürfte nichts besonderes zu bedeuten haben.
  Mit Zitat antworten Zitat
Benutzerbild von Luckie
Luckie

 
Delphi 2006 Professional
 
#20
  Alt 14. Mär 2007, 21:48
Zitat von Elvis:
Erst wenn du nichts mehr aus deinem Code entfernen kannst bist du fertig damit.
Schönes Zitat. Wuirst du jetzt zum Julius Cäsar der Programmierung: "Bello Programaticus"

Zitat:
Sinnloses nil-en von Werten, die eh aus dem Scope rennen (weil das Element nicht mehr in der Liste ist!), halte ich für mehr als überflüssig.
Gewohnheitssache. Wenn man sich so was einfach mal angewöhnt, kann man es nicht vergessen, wenn man es mal braucht.

Zitat:
Außerdem kannst du eine ObjectList mit OwnsObjects = true benutzen, und du musst dich damit nicht mehr befassen.
Und genau deshalb wollte ich sie in dem Tutorial nicht verwenden.

@Thorben_K: Im Augenblick wäre die Klasse TAddressBook natürlich noch überflüssig. Aber wenn man dann mal ein richtiges Adressbuch schreibt kommen ja noch mehr Dinge hinzu, die man dann dort sinnvoll kapseln kann.
Michael
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 2 von 4     12 34      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 02:05 Uhr.
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz