Nachdem Kataloge und Bestände vorhanden sind, soll dem Kunden das Angebot auch angezeigt werden.

Es muß nun ein FormSheet erzeugt und zurückgegeben werden, das die Tabelle mit dem Angebot enthält. Außerdem sollten die FormSheets Buttons enthalten, nämlich zum Verleihen und Zurückgeben von Videos.

FormSheet

Wenn man sich die Klasse FormSheet ansieht, wird man feststellen, daß sie zwei Arten von Konstruktoren hat: Zum einen ist es möglich, einem FormSheet eine Komponente zu übergeben, zum anderem kann man ihm aber auch einen FormSheetContentCreator übergeben. Ein FormSheetContentCreator kann vom FormSheet benutzt werden, um seinen Inhalt zu erzeugen. Die Einführung des ContentCreators in das Framework hat folgende Bewandnis: Es ist nicht immer möglich, Swing-Komponenten korrekt abzuspeichern (zu serialisieren). Daher kann auch ein FormSheet, das mit Hilfe einer Komponente erzeugt wurde, nicht vollständig abgespeichert werden. Ergebnis ist, daß ein solches FormSheet zwar gespeichert und auch wieder geladen wird, jedoch nach dem Laden keinen Inhalt mehr besitzt. Die Swing-Komponente konnte nicht mitgesichert und so auch nicht wiederhergestellt werden. Lösung ist die Einführung eines FormSheetContentCreators. Dieser besitzt eine Methode createFormSheetContent, die jedesmal aufgerufen wird, wenn der Inhalt des FormSheets neu erzeugt werden muß -- z.B. nach dem Laden. Es sollte also jedes FormSheet, das konkret wiederhergestellt werden soll, mit einem FormSheetContentCreator ausgestattet werden.

FormSheetContentCreator

Ein weiterer Vorteil dieses Konzepts ist, daß einem FormSheet mit addContentCreator weitere Objekte vom Typ FormSheetContentCreator übergeben werden können. Diese werden beim Aufbauen des FormSheets nacheinander aufgerufen und können jeder für sich Veränderungen vornehmen, so daß an einem vorgefertigten FormSheet weitere Anpassungen durch das Hinzufügen eines ContentCreators möglich sind.

Es soll im folgenden ein FormSheetContentCreator erstellt werden, der in seiner createFormSheetContent-Methode die gewünschte Tabelle und die benötigten Buttons erzeugt. Mit dessen Hilfe wird dann in der getDefaultFormSheet-Methode ein FormSheet erzeugt und zurückgegeben. Es wird zunächst die Klasse DefaultCounterFormCreator wie folgt mit Code gefüllt:


  import sale.*;

  public class DefaultCounterFormCreator extends FormSheetContentCreator
  {
    public DefaultCounterFormCreator()
    {
      super();
    }
  }
        

Es wird, wie oben schon erwähnt, die Methode createFormSheetContent in diese Klasse eingefügt. Sie enthält als Parameter das FormSheet, mit dem es arbeiten soll:

FormSheetContent


  public void createFormSheetContent(FormSheet map)
  {
  }          
        

Zunächst soll die Tabelle mit den Videos in das FormSheet eingefügt werden. Dazu muß der Videobestand aus der globalen Liste der Bestände herausgesucht werden:


  CountingStockImpl cs =
    (CountingStockImpl)Shop.getTheShop().getStock("Video-Countingstock");
        

Es wird mit der statischen Methode getTheShop der derzeit aktive Shop ermittelt und danach von ihm der benötigte Stock geholt.

Um dem Compiler die Klasse CountingStockImpl bekannt zu machen, ist eine weitere import-Anweisung zu der schon vorhandenen hinzuzufügen:


  import data.ooimpl.*;
        

Um die Tabelle zu erstellen, kann eine der statischen Methoden von SingleTableFormSheet benutzt werden. Diese Klasse aus dem Paket data.stdforms ist ideal zum Erstellen von FormSheets, die eine Tabelle mit dem Inhalt eines Stocks, Catalogs oder DataBaskets enthalten sollen. Alles was zu tun ist, ist die passende create-Methode aufzurufen.

SingleTableFormSheet

Ganz so einfach ist es dann aber leider doch nicht. Es gibt zwar eine create-Methode, die lediglich einen Namen, einen CountingStock und ein UIGate, das null sein kann, erwartet (UIGates werden später erklärt). Diese ist aber nicht verwendbar, da sie die Werte vom Typ QuoteValue, die sie von den darzustellenden VideoCassettes über getValue bekommt, nicht verarbeiten kann. Es bleibt also nichts anderes übrig, als die create-Methode zu verwenden, die zusätzlich einen TableEntryDescriptor (kurz TED), also eine Beschreibung des Tabelleneintrags erwartet und ihren eigenen, noch zu erstellenden TableEntryDescriptor zu übergeben.

TableEntryDescriptor

Auf den ersten Blick sollte man meinen, ein derartiger Descriptor ließe sich am besten von DefaultCountingStockItemTED ableiten. Auf den zweiten Blick hat aber die Ableitung von einer Unterklasse von DefaultCountingStockItemTED einen bedeutenden Vorteil: DefaultMoneyBagItemTED macht im wesentlichen das gleiche wie seine Oberklasse, es stellt aber den Wert des Eintrags als Währungsangabe dar, nicht einfach als normale Zahl. Diese Klasse ist zwar ursprünglich für Einträge eines MoneyBags gedacht, daher auch der Name, ist aber auch für alle anderen CountingStocks geeignet, deren Wert einen Geldbetrag bezeichnet.

Es wird die Klasse OfferTED wie folgt erstellt:


  import data.swing.*;

  public class OfferTED extends DefaultMoneyBagItemTED
  {
  }
        

Die Tabelle soll zunächst nur zwei Spalten enthalten: zum einen den Namen der Videos und zum anderen deren Preis. Später jedoch, wenn es darum geht, Videos zu verleihen, soll der Kunde auch erfahren, wieviele Videos überhaupt verfügbar sind. Es wird also dem Konstruktor von OfferTED ein Boolescher Wert übergeben, der angibt, ob die dritte Spalte, die die Anzahl der verfügbaren Videos enthält, angezeigt werden soll oder nicht.

Dazu wird in der Klasse zunächst folgende Variable deklariert:


  private boolean withCount;
        

Nun folgt der Konstruktor. Dieser ruft zunächst den Konstruktor der Oberklasse mit der zu verwendenden Währung auf. Die Währung wird auf schon bekannte Weise vom aktuellen Shop aus der globalen Katalogliste geholt. Außerdem wird die gerade deklarierte Variable initialisiert:


  public OfferTED(boolean withCount)
  {
    super((Currency)Shop.getTheShop().getCatalog("DM"));
    this.withCount = withCount;
  }
        

Um dem Compiler die im Konstruktor verwendeten Klassen Currency und Shop bekannt zu machen, sind zwei weitere import-Anweisungen notwendig:


  import data.*;
  import sale.*;
        

Als nächstes muß die Methode getColumnCount implementiert werden. Sie gibt an, aus wieviel Spalten die Tabelle bestehen soll. Der Rückgabewert ist abhängig von withCount:

Spaltenanzahl


  public int getColumnCount() 
  {
    return withCount?3:2;
  }
        

Um den Spaltentitel zu verändern, wird die Methode getColumnName angepaßt:

Spaltentitel


  public String getColumnName(int nIndex)
  {
    return (new String[]{ "Name",
                          "Price",
                          "Available" }) [nIndex];
  }
        

Die wichtigste Methode, die angepaßt werden muß, ist jedoch getValueAt. Wie angesprochen wurde, ist es DefaultCountingStockItemTED und damit auch DefaultMoneyBagItemTED nicht möglich, QuoteValues als Werte zu verarbeiten. Daher muß in getValueAt der Wert der VideoCassettes aufgesplittet und nur der Verkaufspreis zurückgegeben werden. Alle anderen Felder der VideoCassette können ganz normal ausgewertet und daher die betreffenden Anfragen an die Oberklasse weitergeleitet werden.

Um die VideoCassette aufsplitten zu können, muß sie erst einmal ermittelt werden. Dazu ist es nötig, die Klasse herauszubekommen, von der der übergebene Record ist. Diese entspricht der Klasse des Rückgabewertes der getRecord-Methode des verwendeten TableModels. Aus der Dokumentation zu MoneyBagItemTED erfährt man, daß es mit CountingStockTableModel zusammenarbeitet. Dessen getRecord-Methode wiederum liefert Records vom Typ CountingStockTableModel.Record an die Tabelle.

TableModel

Die getValueAt-Methode sieht also folgendermaßen aus:


  public Object getValueAt(Object oRecord, int nIndex)
  {
    if (nIndex == 1) {
      VideoCassette vidCassette =
        (VideoCassette)
          ((CountingStockTableModel.Record)oRecord).getDescriptor();
      return ((QuoteValue)vidCassette.getValue()).getBid();
    }
    else return super.getValueAt(oRecord, nIndex); 
  }
        

Damit ist die Erstellung des TableEntryDescriptors abgeschlossen.

Es kann nun in DefaultCounterFormCreator unter der schon bestehenden Anweisung weitergearbeitet werden. Dort wird, wie angesprochen, über eine der create-Methoden aus SingleTableFormSheet ein FormSheet erzeugt, das die gewünschte Tabelle enthält. Die createFormSheetContent-Methode wird um folgende Zeilen erweitert:

FormSheet erzeugen


  FormSheet fs =
    SingleTableFormSheet.create("Available Videocassettes", cs,
                                null, new OfferTED(false));            
        

Die Parameter bedeuten dabei:

  • Titel des FormSheets
  • darzustellender CountingStock
  • UIGate: hier null (zur Bedeutung später)
  • der zu verwendende TableEntryDescriptor(TED)

Außerdem muß eine import-Anweisung ergänzt werden, denn SingleTableFormSheet ist aus dem Paket data.stdforms:


  import data.stdforms.*;
        

Auf diese Weise hat man ein FormSheet mit der Tabelle der Videos erhalten. Leider soll diese Tabelle jedoch nicht in irgendeinem FormSheet enthalten sein, sondern in dem, das createFormSheetContent als Parameter übergeben wurde. Daher wird die in dem neuen FormSheet befindliche Komponente ermittelt und dem übergebenen FormSheet zugewiesen. Also wird die Methode createFormSheetContent wie folgt erweitert:


  map.setComponent(fs.getComponent());  
        

Die Tabelle befindet sich nun an der gewünschten Stelle. Es sind jetzt noch Buttons einzufügen. Dazu werden zunächst alle eventuell schon im FormSheet vorhandenen Buttons entfernt:


  map.removeAllButtons();
        

Die beiden benötigten Buttons "rent" und "give back" werden ergänzt:


  map.addButton("rent", 1, 
    new sale.Action()
    {
      public void doAction(SaleProcess p, SalesPoint s)
      {
        //Code zum Ausführen des Verleihvorgangs
      }
    }
  );

  map.addButton("give back", 2, 
    new sale.Action()
    {
      public void doAction(SaleProcess p, SalesPoint s)
      {
        //Code zum Ausführen des Rückgabevorgangs
      }
    }
  );        
        

Die an addButton übergebenden Parameter sind die Aufschrift des Buttons, eine Nummer zur Identifikation und die auszulösende Aktion. Sind schon Buttons im FormSheet vorhanden, muß darauf geachtet werden, daß schon vergebene Nummern nicht noch einmal verwendet werden.

Die auszulösende Aktion implementiert das Action-Interface aus dem Paket sale. Dieses Paket verlangt eine Methode doAction. In ihr steht der Code, aus dem die Aktion besteht, die beim Betätigen des Buttons ausgeführt werden soll. In diesem Fall bleiben die Methoden noch leer und werden in einem späteren Abschnitt implementiert.

Der Aufbau der FormSheets ist nun abgeschlossen. Damit die Kunden das Angebot betrachten können, muß der eigentliche "Verkaufsstand" implementiert werden, an dem die Videos ausgeliehen und zurückgegeben werden können - der Counter.

Kasse


  import sale.*;

  public class Counter extends SalesPoint
  {
    public Counter(String name)
    {
      super(name);
    }
  }
        

Hier wird im Konstruktor der Konstruktor von SalesPoint aufgerufen und der Name des Counters übergeben.

Außerdem ist eine Methode notwendig, die das Standard-FormSheet für den Counter zurückgibt. Die vorhin erstellte Klasse DefaultCounterFormCreator wird zum Erstellen der Oberfläche verwendet:


  public FormSheet getDefaultFormSheet()
  {
    return new FormSheet("Menu",
                         new DefaultCounterFormCreator(),
                         false);
  }
        

Der Counter muß nun bei VideoMachine angelegt und als SalesPoint angemeldet werden. Dies geschieht in der main-Methode. Direkt unter dem Erzeugen der Log-Datei wird die Klasse wie folgt erweitert:


  Counter c = new Counter("Video Rental");
  c.attach(new DataBasketImpl());
  c.attach(new User("SalespointUser"));
  vidMachine.addSalesPoint(c);
        

An den SalesPoint wird ein DataBasket "angehängt". In diesem Warenkorb werden die Videos des Kunden nach deren Auswahl so lange aufbewahrt, bis er sie endgültig ausleiht. Der Nutzer des Counters muß der Klasse ebenfalls bekannt gemacht werden. Da User ein Bestandteil des Pakets users ist, werden die import-Anweisungen von VideoMachine um eine weitere ergänzt:

DataBasketImpl


  import users.*;
        

Das Angebot kann jetzt betrachtet werden.

Hier der Quelltext der in diesem Kapitel geänderten Klassen:

Kataloge und Bestände Der Kunde

last modified on 24.09.2001
by kk15 and ch17