Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Probleme mit selbst erstellter Klasse (https://www.delphipraxis.net/47014-probleme-mit-selbst-erstellter-klasse.html)

XiONLuNiX 5. Jun 2005 08:16


Probleme mit selbst erstellter Klasse
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo,

erstmal entschuldigung für die schlechte Überschrift, aber ich weis wirklich nicht wie ich mein Problem betiteln soll.

Es geht um folgendes, in der Schule lerne ich zur Zeit Delphi und wir sollten eine (Warenhaus-)Kasse simulieren.
Dazu sollte eine Schlange(FIFO, TSchlange) als Klasse erzeugt werden, welche dann Kunden(TKunde) Verwaltet.
Die Kunden werden vom Hauptformular in bestimmten Abständen erzeugt und von Zeit zur Zeit werden die Kunden "bedient"
indem ihrem Warenkorb ein Gegenstand entnommen wird und "gescannt" wird (Verringerung der TKunde-Variable "Anzahl" um 1).
Das ganze wird in einer ListBox dargestellt.
Es sollte dann so ablaufen, dass wenn der Kunde keinen Gegenstand mehr im Warenkorb hat (Anzahl = 0) der Kunde "bezahlt" (destroy) und der nächste Kunde bedient wird.

Bis dahin bin ich leider erst gar nicht gekommen. Bei mir tritt nämlich folgender Fehler auf:
Anstatt, dass ein neuer Kunde erzeugt und in die Schlange eingereiht wird (enqueue), wird das Aktuelle Element manipuliert.

Ich habe schon ziemlich viel Zeit darauf verbracht den Fehler zu finden. Doch weder durch meinen Lehrer noch durch mich konnte der Fehler behoben werden.

Ich habe mien Programm angehängt.

Ich hoffe, dass ihr mir helfen könnt.

Vielen Dank

>X!<

marabu 5. Jun 2005 13:53

Re: Probleme mit selbst erstellter Klasse
 
Hallo,

ich habe mir dein Projekt herunter geladen. Am Quelltext erkenne ich, dass du wirklich noch nicht viel programmiert hast, aber da sind auch noch fundamentale Fehler. Ich habe dein Programm einfach mal gestartet und mir das Ergebnis angesehen. Also das Anstellen an der Schlange scheint auf den ersten Blick zu funktionieren, die Kunden werden immer mehr, der Kassierer kommt einfach nicht nach.

Wenn der Sinn der Übung darin besteht, eine FIFO-Struktur zu implementieren (die Klasse Contnrs.TQueue kann alles was du brauchst), dann solltest du das auch tun, bevor du zu Kunden und Kasse übergehst. Für die FIFO-Struktur kannst du wählen:
  • einfach verkettete Liste
  • array
Du hast dich für die einfach verkettete Liste entschieden. Dann muss der Zeiger auf den Nachfolger aber Bestandteil der Struktur sein, die du in der Liste verwalten willst - bei dir gibt es statt dessen eine globale Variable "Nach". Wenn das eine Weile funktioniert, dann ist das Glückssache.

Grüße vom marabu

XiONLuNiX 5. Jun 2005 18:26

Re: Probleme mit selbst erstellter Klasse
 
Hallo,

erstmal vielen Dank für die Mühe.
Dass alle Variablen, die in ner Klasse unter var stehen, global sind wusste ich bisher nicht.
Ausserdem wurde uns die einfach verkettete Liste vorgegeben, wobei mir arrays eigentlich lieber sind.

Du hast fundamentale Fehler erwähnt. Könntest du das bitte ein wenig konkretisieren?!
Da ich noch am Anfang mit Delphi stehe und meine Begegnungen mit Delphi fast ausschließlich
nur theoretischer Natur waren (also, lesen, verstehen, im Unterricht wiedergeben),
bin ich grad erst dabei mir praktische Erfahrung anzueignen.
Wohin das führt, hast du ja gesehen :-D


>X!

marabu 5. Jun 2005 20:13

Re: Probleme mit selbst erstellter Klasse
 
Der fundamentale Fehler schlechthin ist die Sache mit den globalen Variablen Nach und Anzahl. Anzahl gehört eigentlich zum Gedächtnis deines Kunden-Objektes. Mit dem Verweis auf den Nachfolger ist das allerdings so eine Sache. Der Grad der Entkopplung deiner Klassen bestimmt wo dieser Verweis unterzubringen ist.

Denkbar sind folgende Ansätze:

(1) Deine Queue ist spezifisch und kann nur Objekte einer bestimmten Klasse verwalten.
(2) Deine Queue ist generisch und kann Objekte einer beliebigen Klasse verwalten.
(2.1) Die Klasse TKunde ist Nachfahre einer speziellen Klasse TQueueItem.
(2.2) Die Klasse TQueueItem enthält einen Verweis auf ein Kunden-Objekt.

Ich favorisiere Ansatz (2) und bei deiner Aufgabe dürften die beiden dort unterschiedenen Varianten beide problemlos umsetzbar sein.

Delphi-Quellcode:
// Variante (2.1)

type
  TQueueItem = class
  private
    FNext: TQueueItem;
  public
    property Next: TQueueItem read FNext write FNext;
  end;
 
  TKunde = class(TQueueItem)
  private
    FAnzahl: integer;
  public
    constructor Create(anzahl: integer);
    procedure Auflegen; // Einen Artikel zum Erfassen geben
    property Anzahl: integer read FAnzahl; // write nur intern
    property Naechster: TKunde read FNext;
  end;
Delphi-Quellcode:
// Variante (2.2)

type
  TQueueItem = class
  private
    FNext: TQueueItem;
    FData: TObject;
  public
    consturctor Create(data: TObject);
    property Data: TObject read FData;
    property Next: TQueueItem read FNext write FNext;
  end;
 
  TKunde = class
  private
    FAnzahl: integer;
  public
    constructor Create(anzahl: integer);
    procedure Auflegen; // Einen Artikel zum Erfassen geben
    property Anzahl: integer read FAnzahl; // write nur intern
  end;
Früher oder später solltest du dir angewöhnen abstrakte Klassen (Queue) und solche aus deinem Aufgabenfeld (Kunde, Kasse) voneinander zu trennen. Hier noch ein Code-Schnippsel für TQueue:

Delphi-Quellcode:
  TQueue = class
  private
    FHead: TQueueItem;
    FTail: TQueueItem;
  public
    function Add(qi: TQueueItem): TQueueItem;
    function Remove: TQueueItem;
    property Head: TQueueItem read FHead;
  end;
Ein anderer Fehler ist deine Verwendung von Randomize(). Diese Prozedur dient eigentlich dazu dem Pseudo-Zufallszahlen-Generator einen bestimmten Seed-Wert zu verpassen, damit er eine reproduzierbare Folge von Zufallszahlen erzeugt. Brauchst du eigentlich nicht wirklich - schon gar nicht im Konstruktor deiner Kunden-Klasse.

Bevor ich deine Hausaufgaben jetzt ganz erledige warte ich lieber mal darauf, was du so zu Stande bringst.

marabu

XiONLuNiX 6. Jun 2005 19:00

Re: Probleme mit selbst erstellter Klasse
 
Also, eigentlich kriegen wir die Deklarationen aus nem Buch und müssen die Methoden dann nur "füllen",
was manchmal ziemlich in die Hose geht, da die Methoden kaum beschrieben werden und die Namen auch nicht gerade aussagekräftig sind.
Ich hab versucht die Kassen so selbstständig wie möglich zu machen und das ist das Ergebnis:

Delphi-Quellcode:
unit mKasse;

interface
        uses Classes, StdCtrls, ExtCtrls, mKunde;
 type TSchlange = class(TObject)
        private
         fAktuell,fEnde :TKunde;
         TimKunde, TimBedienung: TTimer;
         fLstKasse: TListBox;
         function IsEmpty: boolean; virtual;
         procedure TimKundeTimer(Sender:TObject);
         procedure TimBedienungTimer(Sender:TObject);
         procedure Verwaltung(P: boolean); virtual;
        public
         constructor Create(List: TListBox); virtual;
         procedure Enqueue(Elem: TKunde); virtual;
         procedure Dequeue; virtual;
         property Element: TKunde read fAktuell;
         property Ende: TKunde read fEnde;
         property Offen: Boolean write Verwaltung;
         end;


implementation
constructor TSchlange.Create(List: TListBox);
begin
    fLstKasse := List;
    TimKunde := TTimer.Create(nil);
    With TimKunde do begin
          Interval := 1000;
          Enabled := false;
          OnTimer := TimKundeTimer;
          end;

    TimBedienung := TTimer.Create(nil);
    With TimBedienung do begin
          Interval := 250;
          Enabled := false;
          OnTimer := TimBedienungTimer;
          end;
end;

procedure TSchlange.Enqueue(Elem: TKunde);
var SAnzahl : String;
 begin
        if not self.IsEmpty then
                Ende.Nach := Elem
            else
                fAktuell := Elem;
        fEnde := Elem;

        Str(fEnde.Anzahl,SAnzahl);
        fLstKasse.AddItem(SAnzahl,self);
 end;

procedure TSchlange.Dequeue;
var temp: TKunde;
 begin
      if not self.IsEmpty then begin;
         temp := fAktuell;
         fAktuell := temp.Nach;
         temp.Destroy;
         if fAktuell = nil then fEnde := nil;
      end;
 end;

function TSchlange.IsEmpty: boolean;
 begin
      If fAktuell <> nil then IsEmpty := false
                        else IsEmpty := true;
 end;

procedure TSchlange.TimKundeTimer(Sender:TObject);
begin
   Enqueue(TKunde.Create);
end;

procedure TSchlange.TimBedienungTimer(Sender:TObject);
var num: string;
begin
        if not IsEmpty then begin
         fAktuell.Gescannt;
         fLstKasse.Items.Delete(0);
         if fAktuell.Anzahl = -1 then Dequeue
                                else begin
                                     str(fAktuell.Anzahl,num);
                                     fLstKasse.Items.Insert(0,num);
                                 end;
         end;
         if (IsEmpty and (TimKunde.Enabled = false)) then TimBedienung.Enabled := false;
end;

procedure TSchlange.Verwaltung(P: boolean);
begin
  TimKunde.Enabled := P;
  if p = true then TimBedienung.Enabled := p;
end;

end.
Delphi-Quellcode:
unit mKunde;

interface
 type
     TKunde = class(TObject)
      private
       fanzahl :integer;
       fNach : TKunde;
      public
       constructor Create; virtual;
       procedure  Gescannt;
       property Anzahl: Integer read fAnzahl;
       property Nach: TKunde read fNach write fNach;
      end;


implementation
 constructor TKunde.Create;
  begin
   inherited create;
   randomize;
   fanzahl := random(9) +1
  end;


  procedure TKunde.Gescannt;
   begin
    fanzahl := fanzahl -1;
   end;


end.
Die Kasse stellt so gesehen selber Kunden an und bearbeitet diese,
so dass das Formular nur die ListBox bereitstellen muss.
Es funzt, aber ich schätze mal, dass die Lösung nicht gerade elegant ist. :oops:

marabu 7. Jun 2005 18:51

Re: Probleme mit selbst erstellter Klasse
 
Ich kann jetzt nicht genau erkennen, was Vorgabe ist und nicht verändert werden darf. Du sagst auch, dass dein Code funktioniert. Deshalb gebe ich nur noch ein paar zaghafte Hinweise.

Denke an das, was ich dir zu Randomize geschrieben habe. Du kannst diese Funktion einfach weglassen. Nur wenn du eine Testreihe öfter wiederholen willst, solltest du die Initialisierung mit identischem Seed-Wert an geeigneter Stelle vornehmen - z.B. vor dem Start der message pump im Projektquelltext.

Deine Timer sind ganz gewiss keine Eigenschaften einer Kasse, sondern eigentlich nur Hilfsmittel für die Ablaufsteuerung. Ich würde die Timer bei der Form ansiedeln. Noch besser wäre auf sie zu verzichten und die Erzeugung von neuen Kunden und den Vorgang des Abkassierens über Buttons interaktiv zu steuern.

Dass die Kasse selbst ihre neuen Kunden erzeugt und am Ende der Warteschlange anstellt, ist nicht sehr realistisch - auch wenn es funktionieren sollte. Da wäre eine Kunden-Factory der bessere Ansatz, aber ob der in deinem Buch besprochen wird? Wohl eher nicht.

Ist dir eigentlich schon aufgefallen, dass du gar keine Klasse TKasse hast?

Gescannt klingt so, als ob dem Kunden vom Kassierer mitgeteilt wird, dass ein Artikel erfasst wurde. Hier wie schon beim Anstellen sehe ich die aktive Rolle eher beim Kunden. Nicht der Kassierer nimmt sich einen Artikel aus dem Warenkorb des Kunden, sondern der Kunde legt einen Artikel zum Scannen auf.

Die meisten Kopfschmerzen bei deinem Projekt machen mir die Mängel im Entwurf, aber wenn das Vorgaben aus deinem Buch sind, wirst du wohl am besten so weiter machen.

Viel Erfolg

marabu


Alle Zeitangaben in WEZ +1. Es ist jetzt 10:57 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