|
|
|
|
|
Nachdem es dem Kunden möglich ist, das Angebot zu
betrachten, soll er auch Videos ausleihen können. Dazu
muß sich der Kunde am Automaten mit seiner Kundennummer
identifizieren, sich Videos aussuchen und den entsprechenden
Preis bezahlen.
|
|
|
Vorgänge dieser Art werden im Framework von
Prozessen bearbeitet. Ein Prozeß kann
benutzt werden, um beliebige Datenstrukturen zu
manipulieren. Derartige Datenstrukturen können
u.a. Objekte der vom Framework bereitgestellten Datentypen
DataBasket , Stock und Catalog
sein.
|
Prozesse
|
|
Prozesse werden im Framework von der Klasse SaleProcess implementiert. Sie werden als
endliche deterministische Automaten verstanden,
deren Zustandsübergänge sowohl durch
Benutzereingaben, als auch spontan ausgelöst werden
können. Die Zustände werden durch
Objekte vom Typ Gate , die
Zustandsübergänge durch Objekte vom Typ Transition realisiert.
|
Gate und Transition
|
|
Eine Transition wird als eine
unteilbare (atomare) Operation abgearbeitet, sie wird vom
Framework nicht unterbrochen. Daher darf es bei der
Abarbeitung einer Transition auf keinen
Fall zu einer Kommunikation mit dem Benutzer oder irgendeiner
anderen langwierigen, möglicherweise endlosen Aktion
kommen.
|
|
|
Im Gegensatz zu Transitions dürfen
Gates unterbrochen werden (signalisiert
durch eine InterruptedException ). Für die
Nutzerinteraktion gibt es eine spezielle Art von Gates , die UIGates .
Es ist vorgesehen, an ihm ein FormSheet
oder ein MenuSheet (oder beides) zu
setzen. Die Entscheidung, welche der von einem UIGate wegführenden Transitionen auszuwählen ist, hängt
von der Benutzereingabe ab.
|
UIGate
|
|
Das Prozeßmodell ist geeignet, selbst komplizierte
Abläufe zu planen und umzusetzen.
|
|
|
Für den Ausleihvorgang werden fünf Gates zusätzlich zu den vom Framework
bereits zur Verfügung gestellten benötigt: eines, an
dem sich der Kunde anmeldet, ein zweites, an dem er die Videos
auswählt und ein drittes, an dem die Videos bezahlt
werden. Zwei weitere Gates werden
benötigt, um zu entscheiden, ob genug Geld bezahlt wurde
und ggf. Wechselgeld zu geben.
|
Gates planen
|
|
Außerdem werden vier nichttriviale Transitions benötigt.
"Nichttrivial" heißt, es kann nicht einfach
von einem Gate zum nächsten
gewechselt werden, sondern es ist zusätzlich das
nächste Gate vorzubereiten.
|
Transitionen planen
|
|
Die Zustände und Zustandsübergänge des
Automaten werden in der folgenden Abbildung noch einmal
verdeutlicht:
Abbildung 2.1:
Der Ausleihvorgang als deterministischer Automat
|
|
|
Zunächst jedoch muß der neue SaleProcess angelegt werden. Alle, im Laufe
der Implementation wichtigen, import -Anweisungen
werden sofort hinzugefügt, da inzwischen klar sein
sollte, welche Framework-Pakete für welche Anweisungen
benötigt werden.
|
|
|
 |  |  |
 |
import sale.*;
import sale.stdforms.*;
import data.*;
import data.ooimpl.*;
import data.stdforms.*;
import users.*;
import java.util.*;
import java.text.*;
import java.lang.*;
public class RentProcess extends SaleProcess
{
}
|  |
 |  |  |
|
|
|
Im Konstruktor wird lediglich der Konstruktor der Oberklasse
aufgerufen. Ihm wird der Name für den Prozeß
übergeben:
|
|
|
 |  |  |
 |
public RentProcess()
{
super("RentProcess");
}
|  |
 |  |  |
|
|
|
Außerdem sind die Gates und
Transitions zu deklarieren:
|
Variablendeklarationen
|
|
 |  |  |
 |
protected UIGate capabilityGate;
protected UIGate selectionGate;
protected UIGate rentGate;
protected Gate decisionGate;
protected UIGate getChangeGate;
protected Transition toSelectionTransition;
protected Transition toPayingTransition;
protected Transition toDecisionTransition;
protected Transition toGetChangeTransition;
|  |
 |  |  |
|
|
|
Es soll weitere Variablen geben, die für den Ablauf des
Prozesses wichtige Werte enthalten. Diese sind die zu
verwendende Währung, der zu zahlende Betrag, der gezahlte
Betrag und ein Wert, der aussagt, wie die beiden Beträge
in Relation zueinander stehen. Außerdem wird noch eine
Variable für den Kunden benötigt, der Videos
ausleihen möchte.
|
|
|
 |  |  |
 |
protected data.Currency myCurrency;
private IntegerValue toPayValue;
private IntegerValue paidValue;
private int payAssessment;
private Customer customer;
|  |
 |  |  |
|
|
|
Um den Automaten zum gegebenen Zeitpunkt korrekt und einfach
aufzubauen, wird eine Methode setupMachine
implementiert, die diese Arbeit übernimmt:
|
|
|
 |  |  |
 |
protected void setupMachine()
{
}
|  |
 |  |  |
|
|
|
In dieser Methode wird zunächst die Variable
myCurrency mit der zu verwendenden Währung
belegt. Dies geschieht vornehmlich aus Gründen der
Effizienz und der Übersichtlichkeit - auf diese Weise
muß nicht jedesmal erst der Shop
und von diesem der entsprechende Catalog geholt werden.
|
Währung
|
|
 |  |  |
 |
myCurrency = (Currency)Shop.getTheShop().getCatalog("DM");
|  |
 |  |  |
|
|
|
Nun sollen die UIGates in der Methode
setupMachine angelegt werden. Zunächst das,
an dem sich die Kunden anmelden:
|
UIGates initialisieren
|
|
 |  |  |
 |
capabilityGate = new UIGate(null, null);
|  |
 |  |  |
|
|
|
Da das so erzeugte Gate den
Startzustand des Automaten darstellen soll, wird es keine
Transition geben, die zu ihm hin
führt. Alles, was zur Abarbeitung des Gates nötig ist, muß also vor dem
Starten des Automaten erledigt werden. Da die Methode
setupMachine vor dem Starten ausgeführt
werden soll um den Automaten überhaupt erst
vorzubereiten, ist hier die geeignete Gelegenheit zur
Vorbereitung des Gates . In diesem Fall
ist es das Setzen eines FormSheets zur
Eingabe der Kundennummer. Dazu wird ein TextInputForm verwendet. Dies ist ein FormSheet , das ein Label und eine
Eingabezeile vom Typ JTextInput enthält.
|
Startzustand
|
|
 |  |  |
 |
final TextInputForm tif =
new TextInputForm("Customer-ID", "Customer-ID", "");
|  |
 |  |  |
|
|
|
Es werden die weiteren Gates initialisiert:
|
|
|
 |  |  |
 |
selectionGate = new UIGate(null, null);
rentGate = new UIGate(null, null);
getChangeGate = new UIGate(null, null);
|  |
 |  |  |
|
|
|
Als Parameter werden ein FormSheet und
ein MenuSheet erwartet, die hier aber
nicht benötigt werden. Deshalb wird zweimal
null übergeben. Mehr kann an dieser Stelle
für die Gates nicht getan werden, da deren anzuzeigende
FormSheets vom Ergebnis der vorherigen
Gates abhängig sind.
|
|
|
Der derzeit gültige DataBasket
wird wie folgt ermittelt:
|
|
|
 |  |  |
 |
final DataBasket db = getBasket();
|  |
 |  |  |
|
|
|
Nun sind alle UIGates angelegt. Das
verbleibende Gate , das entscheidet, ob
genug bezahlt wurde und entsprechend reagiert, kann noch nicht
sofort implementiert werden. In seiner
getNextTransition -Methode wird die nächste
Transition zurückgegeben. In
diesem Fall handelt es sich um die zum "Wechselgeld"-Gate führenden Transition , die noch nicht angelegt ist. Im
folgenden werden also alle Zustandsübergänge
implementiert.
|
|
|
Zuerst wird die Transistion
toSelectionTransition unter die Initialisierungen
der Variablen eingefügt:
|
toSelectionTransition
|
|
 |  |  |
 |
toSelectionTransition = new Transition()
{
};
|  |
 |  |  |
|
|
|
Wie man sieht, wird die Transition als
anonyme Klasse realisiert. Dies wird auch mit allen weiteren
Transitions geschehen. In der anonymen
Klasse ist die perform -Methode zu
implementieren:
|
|
|
 |  |  |
 |
public Gate perform(SaleProcess pOwner, User usr)
{
}
|  |
 |  |  |
|
|
|
Diese Methode ist in einer Transition
dafür verantwortlich, alle nötigen Arbeiten
auszuführen, die bis zum Erreichen des nächsten
Gates nötig sind. Dazu
gehört auch das Vorbereiten des nächsten Gates selbst. In diesem Fall führt die
Transition von der erfolgreichen
Eingabe der Kundennummer (capabilityGate ) zu dem
Gate , an dem die Auswahl der Videos
erfolgen soll.
|
|
|
Jetzt füllen wir die Methode perform() mit
Leben. Dazu wird zuerst das Betreten des selectionGate
vorbereitet. Dafür muß der zu verwendende Bestand der
Videos ermittelt werden:
|
|
|
 |  |  |
 |
CountingStockImpl cs =
(CountingStockImpl)Shop.getTheShop().getStock("Video-Countingstock");
|  |
 |  |  |
|
|
|
Nun wird das FormSheet erzeugt, das
beim Betreten des selectionGate angezeigt werden
soll:
|
|
|
 |  |  |
 |
TwoTableFormSheet ttfs =
TwoTableFormSheet.create("Make your Selection",
cs,
db,
selectionGate,
null,
null,
false,
new OfferTED(true),
null,
null);
|  |
 |  |  |
|
|
|
Wie man sieht, besitzt auch TwoTableFormSheet eine Reihe von create -Methoden für viele Kombinationen
von Parametern. An dieser Stelle wurde die Methode mit
folgenden Parametern verwendet:
- Titel des
FormSheets
- Quelle, hier der
CountingStock
- Ziel, hier der
DataBasket
-
UIGate , an dem das
FormSheet darzustellen ist
-
Comparator für die Quelltabelle
-
Comparator für die Zieltabelle
- Aussage, ob die Zeilen mit dem Wert 0 in der
Quelltabelle gezeigt werden sollen
-
TableEntryDescriptor der
Quelltabelle
-
TableEntryDescriptor der
Zieltabelle
- Strategie für die Bewegung der Einträge
|
|
|
Als nächstes muß das FormSheet den Bedürfnissen des
selectionGate angepaßt werden. Dazu wird
hinter der Erzeugung des FormSheets ein
FormSheetContentCreator
eingefügt. Auch er wird als anonyme Klasse implementiert:
|
|
|
 |  |  |
 |
ttfs.addContentCreator(new FormSheetContentCreator()
{
});
|  |
 |  |  |
|
|
|
Im ContentCreator muß nun eine Methode
createFormSheetContent implementiert werden. Sie
wird bei der Erstellung des FormSheets
aufgerufen, um die notwendigen Anpassungen vorzunehmen.
|
|
|
 |  |  |
 |
protected void createFormSheetContent(FormSheet fs)
{
}
|  |
 |  |  |
|
|
|
Zunächst werden alle eventuell vorhandenen Buttons entfernt:
|
|
|
|
|
|
Dann werden zwei Buttons eingefügt. Deren Betätigung
löst jeweils eine neue Transitions
aus. Nach dem Betätigen des "Ok"-Buttons
gilt die Auswahl der Videos als beendet und der Kunde
muß seine Videos bezahlen. Nach Auswahl des
"Cancel"-Buttons wird der Ausleihvorgang
abgebrochen.
|
|
|
 |  |  |
 |
fs.addButton("Ok", 100, new sale.Action()
{
public void doAction(SaleProcess p, SalesPoint sp)
{
selectionGate.setNextTransition(toPayingTransition);
}
});
fs.addButton("Cancel", 101, new sale.Action()
{
public void doAction(SaleProcess p, SalesPoint sp)
{
selectionGate.setNextTransition(
GateChangeTransition.CHANGE_TO_ROLLBACK_GATE);
}
});
|  |
 |  |  |
|
|
|
Das besondere am "Cancel"-Button ist,
daß er zum Rollback-Gate
führt. An diesem werden die bereits ausgewählten und
sich im Datenkorb befindlichen Videos wieder in den Bestand
des Verleihs einsortiert. Eine zum Rollback-Gate führende Transition ist bereits als Konstante CHANGE_TO_ROLLBACK_GATE in GateChangeTransition gespeichert.
|
Rollback
|
|
Die Anpassungen des FormSheets sind
beendet, der FormSheetContentCreator ist nun
vollständig.
|
|
|
Am Ende der perform -Methode muß jetzt noch
das gerade erstellte FormSheet am
richtigen Gate gesetzt und das
nächste Gate zurückgegeben
werden.
|
|
|
 |  |  |
 |
selectionGate.setFormSheet(ttfs);
return selectionGate;
|  |
 |  |  |
|
|
|
Die zum Auswählen führende Transition ist nun abgeschlossen. Als
nächstes wird die toPayingTransition
benötigt, die nach der Auswahl der Videos zum
rentGate führt. Dort wird errechnet, wieviel
Geld der Kunde in den Münzschacht des Automaten werfen
muß, um sich alle gewünschten Videos ausleihen zu
können.
|
|
|
Wie schon bei der ersten Transition
wird auch hier die Methode perform implementiert,
die das nächste Gate vorbereitet:
|
toPayingTransition
|
|
 |  |  |
 |
toPayingTransition = new Transition()
{
public Gate perform(SaleProcess pOwner, User usr)
{
}
};
|  |
 |  |  |
|
|
|
In der perform -Methode muß zunächst
der zu zahlende Betrag ausgerechnet und
toPayValue zugewiesen werden. Dazu wird die
Methode sumBasket benutzt, die im
Interface DataBasket definiert ist. Sie
benötigt als Parameter ein Objekt vom Typ DataBasketCondition , ein Objekt vom Typ BasketEntryValue und einen Initialwert vom
Typ Value , auf den der Inhalt des
Datenkorbes aufsummiert wird.
|
|
|
Das Interface DataBasketCondition
definiert eine Beschreibung eines DataBasketEntry . Sie wird für
Filterzwecke benutzt. In diesem Fall soll eine DataBasketCondition benutzt werden, die alle
im DataBasket vorhandenen StockItems umfaßt. Ein derartiges
Objekt wird mit ALL_STOCK_ITEMS bereits
als Konstante in DataBasketConditionImpl , einer einfachen
Implementierung von DataBasketCondition , zur Verfügung
gestellt.
|
|
|
BasketEntryValue ist ein
Hilfsinterface, das lediglich die Methode getEntryValue enthält. Diese erwartet
als Parameter einen DataBasketEntry und
gibt dessen Wert zurück. Auch für dieses Interface
existieren in BasketEntryValue bereits
Konstanten, die häufige Anwendungsfälle abdecken. In
diesem Fall wird ONLY_STOCK_ITEMS
verwendet, ein BasketEntryValue , der
nur StockItems verarbeiten kann.
|
|
|
Das Interface Value ist bereits
bekannt. In diesem Programm werden Katalogeinträge
verwendet, die QuoteValues zum
Speichern der Werte benutzen. Es muß also ein
Initialwert von diesem Typ angelegt werden. Die Komponenten
von QuoteValues sind IntegerValues .
|
|
|
Um die im Datenkorb enthaltenen StockItems aufzusummieren, wird folgender
Code in die perform -Methode eingefügt:
|
|
|
 |  |  |
 |
final DataBasketCondition dbc =
DataBasketConditionImpl.ALL_STOCK_ITEMS;
BasketEntryValue bev = BasketEntryValues.ONLY_STOCK_ITEMS;
QuoteValue qvSum = new QuoteValue(new IntegerValue(0),
new IntegerValue(0));
pOwner.getBasket().sumBasket(dbc, bev, qvSum);
|  |
 |  |  |
|
|
|
Nun muß aus der Summe der zu zahlende Betrag extrahiert
werden. Ein QuoteValue besteht aus
Einkaufs- und Verkaufspreis. In qvSum befinden
sich somit die Summen der entsprechenden Werte in den
Datenkorbeinträgen. Die Methode getBid liefert die Summe der
Verkaufspreise. Es ist also einzufügen:
|
|
|
 |  |  |
 |
toPayValue = (IntegerValue)qvSum.getBid();
|  |
 |  |  |
|
|
|
Damit ist der zu zahlende Betrag ermittelt. Mit dessen Hilfe
kann nun das FormSheet für das
nächste Gate aufgebaut
werden. Dazu wird erneut eine TextInputForm verwendet.
|
|
|
 |  |  |
 |
FormSheet tif = new TextInputForm(
"Paying",
"You have to pay" + myCurrency.toString(toPayValue) + ".",
myCurrency.toString(toPayValue));
|  |
 |  |  |
|
|
|
Leider besitzt das FormSheet nur einen
"OK"-Button, und auch dieser schließt
zur Zeit lediglich das FormSheet , ohne
weitere Aktionen auszuführen. Es ist also notwendig,
einen FormSheetContentCreator an das
FormSheet zu übergeben, der in
seiner createFormSheetContent -Methode
die nötigen Änderungen vornimmt.
|
|
|
 |  |  |
 |
tif.addContentCreator(new FormSheetContentCreator()
{
protected void createFormSheetContent(FormSheet fs)
{
}
});
|  |
 |  |  |
|
|
|
Da in den, an die Buttons angehängten, Aktionen auf das
FormSheet zugegriffen werden muß,
jedoch aus anonymen Klassen heraus nur als final
erstellte Variablen des umschließenden Blockes verwendet
werden können, wird in
createFormSheetContent zunächst eine
derartige Variable angelegt und in ihr das als Paramter
übergebene FormSheet zugewiesen:
|
|
|
 |  |  |
 |
final TextInputForm tifFinal = (TextInputForm)fs;
|  |
 |  |  |
|
|
|
Außerdem werden alle Buttons entfernt:
|
|
|
|
|
|
Nun wird ein neuer "Ok"-Button eingefügt
-- Action wird wie üblich als
anonyme Klasse implementiert:
|
|
|
 |  |  |
 |
fs.addButton("Ok", 100, new sale.Action()
{
});
|  |
 |  |  |
|
|
|
Die nach dem Betätigen des Buttons auszuführende
Aktion besteht darin, sich die Eingabe als bezahlten Betrag zu
merken und die nächste Transition
zu setzen. Bei der Verarbeitung des eingegebenen Textes
könnte eine ParseException (aus dem Paket
java.text ) auftreten, daher werden die
Anweisungen in einem try -Block eingeschlossen und
die Exception mit einem catch
abgefangen. Im try -Block werden die Videos in den
Bestand des Kunden übernommen, d.h. in seinem
Kundenkonto mit ihrem Ausleihdatum vermerkt. In die
Action wird folgende
doAction -Methode eingefügt:
|
|
|
 |  |  |
 |
public void doAction(SaleProcess p, SalesPoint sp)
{
try {
paidValue = (IntegerValue)myCurrency.parse(tifFinal.getText());
Object date = Shop.getTheShop().getTimer().getTime();
Iterator i = db.iterator(dbc);
while (i.hasNext()) {
CountingStockItemDBEntry cassetteItem =
(CountingStockItemDBEntry)i.next();
int number = cassetteItem.count();
for (; number-- > 0;)
customer.addVideoCassette(
new CassetteStoringStockItem(
cassetteItem.getSecondaryKey(),
date));
}
rentGate.setNextTransition(toDecisionTransition);
}
catch (ParseException pexc) {
}
}
|  |
 |  |  |
|
|
|
Um nun die ParseException korrekt zu behandeln,
soll eine Meldung ausgegeben werden. Dazu wird eine MsgForm mit dem entsprechenden Text erzeugt
und mit Hilfe der popUpFormSheet -Methode des ProcessContextes angezeigt. Nun erzeugt
jedoch unter Umständen auch popUpFormSheet eine
Exception . Diese wird hier ignoriert. Der
schlimmste Fall, der eintreten kann, ist daß das MsgForm so schnell wieder verschwindet, das
die Meldung nicht gelesen werden konnte. In diesem Fall wird
der Benutzer entweder allein die richtige
Schlußfolgerung ziehen und das Format des von ihm
eingegebenen Textes korrigieren, oder aber er wird nochmal auf
"Ok" drücken und die Fehlermeldung doch
noch erhalten. Es wird in den catch -Block
folgendes eingefügt:
|
|
|
 |  |  |
 |
MsgForm mf = new MsgForm("Error",
"The specific amount does not have " +
"an appropriate format.");
try {
p.getContext().popUpFormSheet(p, mf);
}
catch(InterruptedException iexc) {
}
|  |
 |  |  |
|
|
|
Damit ist ein neuer "Ok"-Button mit
angemessener Aktion eingefügt. Es folgt ein
"Back"-Button, der einfach eine neue GateChangeTransition als nächste Transition setzt. Diese führt ohne
weitere Aktion zurück zur Auswahl:
|
|
|
 |  |  |
 |
fs.addButton("Back", 101, new sale.Action()
{
public void doAction(SaleProcess s, SalesPoint sp)
{
rentGate.setNextTransition(
new GateChangeTransition(selectionGate));
}
});
|  |
 |  |  |
|
|
|
Ähnlich einfach gestaltet sich der
"Cancel"-Button. Der einzige Unterschied ist,
daß er natürlich nicht zur Auswahl
zurückführt, sondern zum Rollback-Gate .
|
|
|
 |  |  |
 |
fs.addButton("Cancel", 102, new sale.Action()
{
public void doAction(SaleProcess s, SalesPoint sp)
{
rentGate.setNextTransition(
GateChangeTransition.CHANGE_TO_ROLLBACK_GATE);
}
});
|  |
 |  |  |
|
|
|
Die Anpassung des FormSheets ist nun
beendet, der FormSheetContentCreator
vollständig.
|
|
|
Am Ende der perform -Methode muß das gerade
erstellte FormSheet am richtigen
Gate gesetzt und das nächste
Gate zurückgegeben werden.
|
|
|
 |  |  |
 |
rentGate.setFormSheet(tif);
return rentGate;
|  |
 |  |  |
|
|
|
Der Kunde konnte nun am rentGate seine Videos
bezahlen. Die setupMachine -Methode wird um eine
weitere Transition ergänzt. Diese
führt vom rentGate zum
decisionGate . An diesem wird entschieden, ob der
Kunde zuwenig, zuviel oder genau die geforderte Summe in den
Münzschacht geworfen hat. Dementsprechend werden
unterschiedliche Aktionen ausgeführt.
|
|
|
Die toDecisionTransition wird wie folgt erstellt:
|
toDecisionTransition
|
|
 |  |  |
 |
toDecisionTransition = new Transition()
{
public Gate perform(SaleProcess pOwner, User usr)
{
}
};
|  |
 |  |  |
|
|
|
Die Überprüfung des Geldes erfolgt mit Hilfe von
if -Anweisungen innerhalb der
perform -Methode. Außerdem wird der zu
zahlende Betrag zum Geldbestand des Videoautomaten
hinzugefügt, wenn ausreichend Geld bezahlt wurde:
|
|
|
 |  |  |
 |
if (paidValue.compareTo(toPayValue) >= 0) {
if (toPayValue.getValue().intValue() > 0)
((CountingStock)Shop.getTheShop().getStock(
"coin slot")).add("1-Pfennig-Stueck",
toPayValue.getValue().intValue(),
pOwner.getBasket());
if (paidValue.compareTo(toPayValue) == 0)
payAssessment = 0;
else
payAssessment = 1;
}
else
payAssessment = -1;
|  |
 |  |  |
|
|
|
Wurde genau der gewünschte Betrag eingeworfen, wird
payAssessment auf 0 gesetzt, wurde zu viel
bezahlt auf 1 und wurde zu wenig bezahlt auf -1.
|
|
|
Am Ende der perform -Methode muß noch das
decisionGate als nächstes zu betretendes
Gate gesetzt werden:
|
|
|
|
|
|
Das decisionGate wird im folgenden implementiert:
|
decisionGate
|
|
 |  |  |
 |
decisionGate = new Gate()
{
};
|  |
 |  |  |
|
|
|
Im decisionGate steht die
getNextTransition -Methode, die entscheidet,
welche Transition bei welchem Ereignis
ausgelöst wird.
|
|
|
 |  |  |
 |
public Transition getNextTransition(SaleProcess pOwner, User usr)
throws InterruptedException
{
}
|  |
 |  |  |
|
|
|
Sollte der Kunde zu wenig Geld in den Münzschacht des
Automaten eingeworfen haben, wird er erneut zum Bezahlen
aufgefordert. Wurde genau der zu zahlende Betrag gegeben, wird
das Ausleihen beendet und das Commit-Gate erreicht. Erwartet der Kunde
Wechselgeld, so führt die
toGetChangeTransition zum
getChangeGate . Sollte der eigentlich
unmögliche Fall eintreten, daß die Variable
payAssessment mit keinem der drei Werte belegt
ist, tritt das default -Ereignis ein. Es werden
alle laufenden Prozesse beendet und die Anwendung springt zum
Quit-Gate .
|
|
|
Die Auswahl der jeweils auszuführenden Aktion erfolgt mit
Hilfe einer switch -Anweisung in der
getNextTransition -Methode:
|
|
|
 |  |  |
 |
switch(payAssessment) {
case -1:
FormSheet mf = new MsgForm("Error",
"You have to pay more!",
false);
pOwner.getContext().popUpFormSheet(pOwner, mf);
return new GateChangeTransition(rentGate);
case 0:
return GateChangeTransition.CHANGE_TO_COMMIT_GATE;
case 1:
return toGetChangeTransition;
default:
FormSheet mf2 = new MsgForm("Error",
"Internal error at Decision Gate. "
+ "Will quit process.",
false);
pOwner.getContext().popUpFormSheet(pOwner, mf2);
return GateChangeTransition.CHANGE_TO_QUIT_GATE;
}
|  |
 |  |  |
|
|
|
Als letztes wird die noch fehlende
toGetChangeTransition implementiert. Zuerst wird
wieder eine Transition angelegt und in
ihr die perform -Methode implementiert:
|
|
|
 |  |  |
 |
toGetChangeTransition = new Transition()
{
public Gate perform(SaleProcess pOwner, User usr)
{
}
};
|  |
 |  |  |
|
|
|
In der dieser Methode wird die Oberfläche vorbereitet,
die beim Erreichen des neuen Gates
dargestellt werden soll. Es wird ein einfaches MsgForm erzeugt, das den zurückgegebenen
Betrag anzeigt.
|
|
|
 |  |  |
 |
MsgForm mf = new MsgForm(
"GetChange",
"You get "
+ myCurrency.toString(
(IntegerValue)paidValue.subtract(toPayValue))
+ " Change.");
|  |
 |  |  |
|
|
|
Auch hier besitzt das FormSheet nur
einen "OK"-Button, der lediglich das Fenster
schließt. Es ist also wieder notwendig einen FormSheetContentCreator an das FormSheet zu übergeben, der in seiner
createFormSheetContent -Methode dem
Button eine neue Aktion zuordnet. Mit der getButton -Methode wird der Button aus dem
FormSheet geholt, und die setAction -Methode ändert die Aktion, die
vom Buttons ausgelöst wird. In unserer Anwendung wird
zum Commit-Gate gesprungen, da
der Ausleihvorgang mit Rückgabe des Wechselgeldes
abgeschlossen ist.
|
|
|
 |  |  |
 |
mf.addContentCreator(new FormSheetContentCreator()
{
protected void createFormSheetContent(FormSheet fs)
{
fs.getButton(FormSheet.BTNID_OK).setAction(new Action()
{
public void doAction (SaleProcess s, SalesPoint sp)
{
getChangeGate.setNextTransition(
GateChangeTransition.CHANGE_TO_COMMIT_GATE);
}
});
}
});
|  |
 |  |  |
|
|
|
Die perform -Methode ist fertig
implementiert. Nach ihr wird das betreffende FormSheet am zu betretenden
getChangeGate gesetzt, und das Gate , daß als nächstes zu betreten
ist, wird zurückgegeben:
|
|
|
 |  |  |
 |
getChangeGate.setFormSheet(mf);
return getChangeGate;
|  |
 |  |  |
|
|
|
Alle wichtigen Transistions sind
fertiggestellt.
|
|
|
Am Anfang der setupMachine wurde eine TextInputForm erstellt, die die Eingabe der
Kundennummer erwartet. Diese Eingabe muß an den gerade
erstellten Automaten angebunden werden, in dem Buttons
hinzugefügt werden, die bestimmte Aktionen
auslösen. Ohne sie wird man die bereits implementierten
Transitions nie erreichen.
|
InitialGate
|
|
Am Ende der setupMachine -Methode wird der schon
bekannte FormSheetContentCreator
erstellt:
|
|
|
 |  |  |
 |
tif.addContentCreator(new FormSheetContentCreator()
{
protected void createFormSheetContent(FormSheet fs)
{
}
});
|  |
 |  |  |
|
|
|
In der createFormSheetContent -Methode werden
zuerst alle Buttons entfernt:
|
|
|
|
|
|
Nun wird ein neuer "Ok"-Button
eingefügt, der Action als anonyme
Klasse implementieren:
|
|
|
 |  |  |
 |
fs.addButton("Ok", 100, new sale.Action()
{
});
|  |
 |  |  |
|
|
|
Die nach dem Betätigen des Buttons auszuführende
Aktion besteht darin, bei falscher Kundennummer die Eingabe
wiederholen zu lassen. Ist die Kundennummer jedoch ein
Integer , wird ggf. ein neuer Kunde erzeugt und
dann zum selectionGate gewechselt. In die Action wird folgende
doAction -Methode eingefügt:
|
|
|
 |  |  |
 |
public void doAction(SaleProcess p, SalesPoint sp)
{
String customerID = tif.getText();
boolean isNotAnInteger = true;
try {
new Integer(customerID);
isNotAnInteger = false;
}
catch(NumberFormatException nfe) {
isNotAnInteger = true;
}
if(!isNotAnInteger
&& (new Integer(customerID)).intValue() > 0) {
try {
customer = new Customer(customerID);
VideoMachine.addCustomer(customer);
}
catch (DuplicateKeyException dke) {
customer = VideoMachine.getCustomerByID(customerID);
}
capabilityGate.setNextTransition(toSelectionTransition);
}
else {
JOptionPane.showMessageDialog(
null,
"CustomerID must be a positive Number!");
capabilityGate.setNextTransition(
new GateChangeTransition(capabilityGate));
}
}
|  |
 |  |  |
|
|
|
Für JOptionPane.showMessageDialog() ist
die folgende import -Anweisung nötig:
|
|
|
 |  |  |
 |
import javax.swing.JOptionPane;
|  |
 |  |  |
|
|
|
Es fehlt nun noch ein "Cancel"-Button, der
zum Rollback-Gate führt:
|
|
|
 |  |  |
 |
fs.addButton("Cancel", 101, new sale.Action()
{
public void doAction(SaleProcess s, SalesPoint sp)
{
capabilityGate.setNextTransition(
GateChangeTransition.CHANGE_TO_ROLLBACK_GATE);
}
});
|  |
 |  |  |
|
|
|
Der FormSheetContentCreator ist abgeschlossen und
das FormSheet wird am entsprechenden
Gate am Ende der
setupMachine -Methode gesetzt.
|
|
|
 |  |  |
 |
capabilityGate.setFormSheet(tif);
|  |
 |  |  |
|
|
|
Die setupMachine -Methode ist damit
abgeschlossen. Als letztes muß die Methode
getInitialGate implementiert werden, die das
Start-Gate des RentProcess
liefert und ihn in Gang setzt:
|
|
|
 |  |  |
 |
public Gate getInitialGate()
{
setupMachine();
return capabilityGate;
}
|  |
 |  |  |
|
|
|
Um den Prozeß in die Anwendung einzubinden wird in der
DefaultCounterFormCreator -Klasse die
doAction -Methode des
"rent"-Buttons mit einer Zeile Code
ausgestattet:
|
|
|
 |  |  |
 |
s.runProcess(new RentProcess());
|  |
 |  |  |
|
|
|
Viel Spaß beim Ausleihen der Videos!!
|
|
|
|
Hier der Quelltext der in diesem Kapitel geänderten Klassen:
|
|
|
|
|