An dieser Stelle sind nun die Nutzerverwaltung und einige Testnutzer angelegt. Es sollen sich jedoch auch weitere Kunden registrieren können,
die sich in geeigneter Weise anmelden müssen. Hierzu wird der Prozess SaleProcessRegister
erzeugt, der nun im Weiteren
erläutert wird.
Der Prozess fordert zur Eingabe des Nutzernamens, Passwort und Passwortwiederholung auf. Sind alle Angaben korrekt und der Nutzer existiert noch nicht, so wird er beim Klick auf OK angelegt und der Prozess endet erfolgreich. Tritt ein Fehler auf, so springt man zur Eingabemaske zurück und bekommt eine Fehlermeldung. Durch Abbrechen kann der Prozess zu jeder Zeit beendet werden, ohne dass dabei eine Änderung an den internen Daten erfolgt.
Abbildung 6.1: Der Registrierungsprozess als Zustandsdiagramm
Deklaration des Prozesses
Wie im Technischen Überblick erläutert wird, ist ein SaleProcess
eine Folge von Zuständen und
Zustandsübergängen. Die Zustände werden dabei durch das Interface Gate
, die Zustandsübergänge durch das Interface Transition
beschrieben.
Während eine Transition
atomar behandelt, d.h. nicht unterbrochen wird, kann an einem Gate
der Kontrollfluss unterbrochen werden und somit auch Interaktion
mit dem Anwender stattfinden. Eine Implementation von Gate
, die die Kommunikation über ein FormSheet
und/oder MenuSheet
ermöglicht, ist
die Klasse UIGate
.
Im Folgenden wird die Klasse SaleProcessRegister
als Spezialisierung von SaleProcess
implementiert.
Dabei muss die abstrakte Methode getInitialGate()
implementiert werden, welche das Start-Gate
zurückliefert.
Würde der Prozess weitere Gate
s haben, wären analog dazu weitere Methoden hinzuzufügen.
Das ist sinnvoll, um die Definition und Erstellung eines Gate
s möglichst zentral zu haben.
Alternativ wäre es auch möglich dies am Ende einer Transition auszuführen, jedoch würde das u.U. erneut die Übersichtlichkeit mindern.
Bei den weiteren Prozessen im Tutorial mit mehreren Gate
s findet immer die erste Alternative ihre Anwendung.
Etwas abweichend davon wird in diesem Prozess der ContentCreator
des initialen FormSheet
s bei einem Zustandsübergang
wiederverwendet und nicht jedesmal durch die getInitialGate()
-Methode neu erzeugt. Doch mehr dazu im letzten Unterkapitel.
package videoautomat; public class SaleProcessRegister extends SaleProcess { private static final long serialVersionUID = -609214820437461076L; public SaleProcessRegister() { super("Register Proecess"); } protected Gate getInitialGate() { FormSheet register = new FormSheet("Register", new RegisterContentCreator("Please type in your data!"), false); return new UIGate(register, null); } }
Ähnlich wie bei der Anzeige des Videobestandes wird ein FormSheet erstellt. Ihm wird im Konstruktor der Titel und ein FormSheetContentCreator
übergeben.
Das FormSheet
selbst würde nur ein leeres Fenster darstellen. Der Inhalt wird durch die Klasse RegisterContentCreator
erzeugt.
Würde man nun die Anwendung übersetzen, bekäme man einen Fehler oder, falls sie doch zu starten geht, spätestens bei der Betätigung des Register-Buttons.
Dies passiert, weil die Klasse RegisterContentCreator
noch nicht erstellt wurde. Dies geschieht nun im nächsten Abschnitt.
Die Klasse RegisterContentCreator
Wie schon beim SingleTableFormSheet
des Videobestandes, erweitert auch die Klasse RegisterContentCreator
die Klasse FormSheetContentCreator
.
Jedoch kann diesmal ein String
übergeben werden, der als Text über den Eingabefeldern erscheint.
package videoautomat.contentcreator; public class RegisterContentCreator extends FormSheetContentCreator { public RegisterContentCreator(String message){ . . . } protected void createFormSheetContent(FormSheet fs) { . . . } }
Diese Klasse ist also konfigurierbar und man kann ihre Darstellung von außen verändern. Damit bei einer Falscheingabe der Nutzername
nicht erneut eingegeben werden muss und die Werte ausgelesen werden können, bietet diese Klasse zahlreiche get
- und set
-Methoden.
Diese stehen nur für die Daten, die gesetzt oder ausgelesen werden können. Nach außen hin sieht man also nur diese Methoden ohne eigentlich zu wissen, wie die Darstellung
der Daten erfolgt.
Dies ist ein wichtiger Beitrag die Oberfläche von der Anwendungslogik zu trennen. Die Klassen, die die Daten weiterverarbeiten, sind nur an diesen Methoden
interessiert und nicht an ihrer visuellen Darstellung. So wird auch die Änderungsfreundlichkeit erhöht, da so leicht Oberflächenkomponenten ausgetauscht werden können
ohne andere Stellen im Programm anzupassen. Die Klasse bietet folgende Methoden, deren Benutzung später gezeigt wird.
package videoautomat.contentcreator; public class RegisterContentCreator extends FormSheetContentCreator { private JTextArea errorMessage; private JLabel message; private JTextField userName; private JPasswordField password; private JPasswordField confirmedPassword; . . . public String getUserName(){ return userName.getText(); } public char[] getPassword(){ return password.getPassword(); } public char[] getConfirmedPassword(){ return confirmedPassword.getPassword(); } public void setUserName(String userName){ this.userName.setText(userName); } public void setErrorMessage(String message){ errorMessage.setText(message); } }
Die createComponent
Methode erzeugt wie bisher den darzustellenden Inhalt. Diesmal wird aber mit zusätzlichen Swing-Komponenten
gearbeitet, um eine eigene Eingabemaske zu erzeugen. Sie verwendet nun die Daten, die mit den set
- Methoden gesetzt wurden,
um die Daten entsprechend zu visualisieren. Die get
-Methoden lesen die Daten wieder aus den Swing-Komponenten aus.
Die Verwendung von Swing soll an dieser Stelle nicht weiter erläutert werden, dazu finden sich zahlreiche Tutorials im Internet.
Das Interessante liegt hier bei der Zuweisung der Buttons.
package videoautomat.contentcreator; public class RegisterContentCreator extends FormSheetContentCreator { . . . public RegisterContentCreator(String message){ errorMessage = new JTextArea(); this.message = new JLabel(); userName = new JTextField(); password = new JPasswordField(); confirmedPassword = new JPasswordField(); this.message.setText(message); errorMessage.setForeground(Color.RED); errorMessage.setBackground(this.message.getBackground()); errorMessage.setEditable(false); } protected void createFormSheetContent(FormSheet fs) { JOptionPanel panel = new JOptionPanel(null, 16, 4, JOptionPanel.CENTER, JOptionPanel.CENTER); userName.setPreferredSize(new Dimension(150, 20)); panel.addOption("User Name", userName); password.setPreferredSize(new Dimension(150, 20)); panel.addOption("Password", password); confirmedPassword.setPreferredSize(new Dimension(150, 20)); panel.addOption("Confirm Password", confirmedPassword); errorMessage.setPreferredSize(new Dimension(200, 50)); panel.addOption("", errorMessage); fs.setComponent(panel); fs.removeAllButtons(); fs.addButton("OK", 1, new TransitWithAction(new RegisterOKTransition(this))); fs.addButton("Cancel", 2, new RollBackAction()); } }
Wie bereits erwähnt, werden den Buttons wieder Action
Klassen übergeben. Auch diesmal sind es wieder Hilfsklassen, die sich wiederverwenden lassen.
In diesem Fall lösen diese Aktionen einen Zustandsübergang aus. Die beiden Klassen sind wieder selbst implementiert und sehen wie folgt aus:
. . . public class TransitWithAction implements Action { private static final long serialVersionUID = 2264074797074037762L; private Transition transition; public TransitWithAction(Transition transition){ this.transition = transition; } public void doAction(SaleProcess saleProcess, SalesPoint salePoint) throws Throwable { UIGate currentGate = (UIGate)saleProcess.getCurrentGate(); currentGate.setNextTransition(transition); } }
. . . public class RollBackAction implements Action { private static final long serialVersionUID = -6870469433795382578L; public void doAction(SaleProcess saleProcess, SalesPoint salePoint) throws Throwable { UIGate currentGate = (UIGate)saleProcess.getCurrentGate(); currentGate.setNextTransition(GateChangeTransition.CHANGE_TO_ROLLBACK_GATE); } }
Im beiden Fällen wird auf dem aktuellen Gate
die Methode setNextTransition
ausgeführt, um den
Zustandsübergang mit einer entsprechenden Transition
durchzuführen.
Im Falle des OK-Buttons wurde der Action
eine spezielle Transition übergeben und dieser zusätzlich die this
Referenz des RegisterContentCreator
mitgegeben. Warum dies gemacht wurde und wie die Transition aussieht,
wird im nächsten Abschitt erklärt.
Einen Zustandsübergang definieren
Der Technischen Überblick beschreibt, dass
die internen Bearbeitungsvorgänge in einer Transition durchzuführen sind.
Für diesen Fall ist das die RegisterOKTransition
, die prüft, ob der Nutzer schon vorhanden ist und die beiden Passwörter übereinstimmen.
Die Klasse liegt im Package transition
und implementiert das Interface Transition
des Frameworks. Das Interface gibt vor, dass die
Methode perform(SaleProcess sp, User user)
zu implementieren ist.
package videoautomat.transition; public class RegisterOKTransition implements Transition { private static final long serialVersionUID = -600112154098222643L; private RegisterContentCreator creator; public RegisterOKTransition(RegisterContentCreator creator) { this.creator = creator; } public Gate perform(SaleProcess sp, User user) { . . . } }
Wie zu erkennen ist, wird im Konstruktor die Referenz des RegisterContentCreator
übergeben. Dies ist die this
-Referenz aus dem
vorherigen Abschnitt. Da die Transition die Daten aus der Eingabe verarbeitet, braucht sie Zugriff auf dieses Objekt.
Durch die Übergabe im Konstruktor kann die perform(SaleProcess sp, User user)
-Methode nun auf die Daten zugreifen und diese verarbeiten.
public Gate perform(SaleProcess sp, User user) { StringBuffer errors = new StringBuffer(""); if ("".equals(creator.getUserName())){ errors.append("You have to choose a user name!n"); } if (!Arrays.equals(creator.getPassword(), creator.getConfirmedPassword())){ errors.append("The passwords are different!n"); } if (UserManager.getGlobalUM().getUserNames().contains(creator.getUserName())){ errors.append("User already exists!n"); } if (errors.length() != 0){ creator.setErrorMessage(errors.toString()); FormSheet register = new FormSheet("Register", creator, false); return new UIGate(register, null); } UserManager.getGlobalUM().addUser(new AutomatUser(creator.getUserName(), creator.getPassword(), false)); return sp.getCommitGate(); }
Zuerst wird geprüft, ob die Daten korrekt eingegeben wurden. Der erste Vergleich prüft, ob überhaupt ein Nutzername eingeben wurde. Fehlt der Nutzername, so wird eine Fehlermeldung vorbereitet.
Der zweite Vergleich prüft die beiden Passwörter auf Gleichheit und der dritte, ob der Nutzer schon existiert. Zum Auslesen der Daten werden die jeweiligen Methoden genutzt, die
der RegisterContentCreator
dafür zur Verfügung stellt.
Der vierte Vergleich prüft nun, ob ein Fehler auftrat und bestimmt dann die Verzweigung. Man hätte natürlich beim ersten Auftreten eines Fehlers darauf reagieren können. So werden aber gleich alle Fehler erkannt und dem Nutzer mitgeteilt. So spart er sich u.U. mehrere Durchläufe, bis alles korrekt eingegeben ist.
Laut Zustandsübergangsdiagramm geht der Prozess zum ersten Gate zurück, wenn ein Fehler auftritt. Dies passiert im Rumpf des vierten Vergleiches.
In diesem Fall wird der RegisterContentCreator
wiederverwendet und nur der Fehlertext gesetzt. D.h. alle bisherigen Eingaben bleiben bestehen.
Alternative hätte man auch eine neue Instanz erstellen können und über die Methoden die Eingaben gesetzt. Je nach Anwendungsfall kann man sich für den einen oder anderen Weg
entscheiden.
Nach dem Setzen der Fehlernachricht wird ein neues Gate
erstellt und mit dem alten RegisterContentCreator
verknüpft.
Anschließen wird das Gate
zurückgegeben und somit als nächstes angezeigt.
Anstatt das Gate
hier zu erstellen, könnte man auch eine selbst definierte getGate
-Methode im Prozess nutzen, und dann dieses Gate
zurückgeben.
Die Methode könnte auch einige Paramter haben, um, wie in diesem Beispiel, Daten an das FormSheet
übermitteln zu können. Dieses Verfahren wird an späterer Stelle im Tutorial gezeigt.
Trat kein Fehler auf, so wird der Nutzer, unter Verwendung der eingegebenen und geprüften Daten, erstellt und der Prozess wechselt zum CommitGate
,
womit er erfolgreich beendet wäre.
An dieser Stelle kann das Programm erneut übersetzt und gestartet werden. Dabei nicht vergessen, im StartFSContentCreator
beim "Register" - Button, das null
durch new RunProcessAction(new SaleProcessRegister())
zu ersetzen.
Die Nutzerverwaltung | Die Anmeldung |