Trainingsprojekt Kiosk

Wir wollen hier Schritt für Schritt betrachten, wie man die fertige Klasse Queue in einem Projekt verwenden könnte.

Dazu ein Hinweis vorweg: Die (Abitur-)Klasse Queue soll ab jetzt nie mehr verändert werden. Sie steht uns als ein Hilfsmittel von nun an bis zum Abi zur Verfügung.

In unserem ersten – noch vergleichsweise kleinem – Beispiel wird die Warteschlange des Kiosks auf dem Schulhof eines Gymnasiums gemäß folgender Beschreibung modelliert.

Die Schüler sollen sich alle in dieser Warteschlange anreihen, so dass sie in der Reihenfolge ihres Erscheinen bedient werden können. Die Schüler der Sekundarstufe I halten sich an diese Regel. Die Schüler der Sekundarstufe II jedoch nicht. Erscheint ein solcher Schüler, so stellt er sich stets hinter den bisher letzten Oberstufenschüler. Gibt es noch keine anderen Oberstufenschüler, so ist er der neue Erste:

Schüler stehen an.
An unserem Kiosk geht es nicht ganz fair zu.

Kündigt der Pausengong das Ende der Pause an, so verlassen alle Schüler der Sekundarstufe I sofort die Warteschlange. Die Schüler der Oberstufenschüler bleiben jedoch weiterhin bei unveränderter Reihenfolge in der Schlange stehen.

Ein solches Projekt sollte man in jedem Fall zunächst auf dem Papier planen und nicht sofort mit einer Implementierung beginnen. Gerade bei aufwändigeren Projekten kann man sonst nach reichlich Arbeit feststellen, dass man eigentlich anders hätte vorgehen sollen und ärgert sich dann, dass man vieles umkrempeln muss. Daher wollen wir hier mit diesen Schritten vorgehen:

Schritt 1. Wir erklären, welche Klassen man für diese Modellierung verwenden könnte und welche Attribute und Methoden diese Klassen voraussichtlich besitzen sollten.

Schritt 2. Wir erklären, welche Datenstrukur(en) (Arrays, Queues oder später noch weitere) verwendet werden sollten.

Schritt 3. Wir stellen die Beziehungen zwischen den Klassen unter Verwendung der Datenstrukturen(en) in einem Entwurfsdiagramm dar.

Schritt 4. Nun setzen wir unseren Entwurf in Java um.

Schritt 5. Wir fertigen eine Dokumentation unseres Projektes an. Diese sollte folgende Punkte enthalten:

  • Ein Implementationsdiagramm, das alle Klassen und deren Attribute und Methoden enthält.
  • Eine kurze Erläuterung, welche Datenstruktur(en) wo verwendet wurden und wieso.
  • Eine kurze Erläuterung, welchen Zweck die Methoden der verschiedenen Klassen haben. Diese Beschreibung sollte so aufgebaut sein wie die der Methoden der Klasse Queue.

In Schritt 1 könnten wir uns überlegen, dass wir in jedem Fall eine Klasse Schueler benötigen. Diese sollte ein Attribut besitzen, das angibt, ob es sich um einen Oberstufenschüler handelt oder nicht. Außerdem wäre vielleicht noch ein Name nützlich, damit wir die Schüler voneinander unterscheiden können. Die Methoden bestehen aus den Standardmethoden (Konstruktor, Getter, und vielleicht auch Methoden zur Ausgabe von Infos in der Konsole).

Außerdem brauchen wir eine Klasse Kiosk, die das gesamte Geschehen koordiniert. Sie sollte eine Methode zum Anstellen von Schülern und auch eine zum Bedienen eines Schülers besitzen. Außerdem sollte sie eine Methode besitzen, die den Pausengong simuliert.

Kommen wir zu Schritt 2. Dass man hier auf die Datenstruktur Queue zurückgreifen soll, ist sicher keine Überraschung, denn eine Warteschlange war ja gerade die Grundlage für unseren Entwurf dieser Struktur. Jedoch können wir mit einer einzigen Queue nicht ohne Weiteres das Verhalten der Schüler simulieren. Hier hilft ein kleiner Trick: Wir benutzen zwei Queues! In die erste kommen die Oberstufenschüler, in die zweite die der Sekundarstufe I. Beim Bedienen werden immer die Oberstufenschüler bevorzugt, d.h., erst wenn ihre Schlange leer ist, wird die Schlange der jüngeren Schüler abgearbeitet.

Ein Diagramm mit den genannten Klassen.

Das Entwurfsdiagramm in Schritt 3 könnte so aussehen:

In diesem Fall ist es schon sehr detailliert, so dass es bereits als Implementationsdiagramm durchgehen könnte. In komplexeren Situationen darf es auch zunächst etwas gröber ausfallen.

In Schritt Schritt 5 kommt wohl der für viele spannendste Teil: Die Implementierung. Die Klasse Queue müssen wir nicht mehr implementieren, sondern wir können sie einfach übernehmen. Es empfiehlt sich nun, zunächst die Klasse Schueler umzusetzen, da ohne sie die Klasse Kiosk nicht arbeiten kann. Hier eine denkbare Implementierung:

public class Schueler {

    private String name;
    private boolean oberstufe;
    
    public Schueler(String pName, boolean pOberstufe) {
        name = pName;
        oberstufe = pOberstufe;
    }

    public String getName() {
        return name;
    }

    public boolean getObertufe() {
        return oberstufe;
    }

    public void schreibeInfo() {

        System.out.print(name);
        
        if (getObertufe()) {
            System.out.println(" Oberstufenschüler");
        } else {
            System.out.println(" Unter- oder Mittelstufenschüler");
        }

    }

}

Nun kommen wir zum Kiosk. Der Anfang ist noch recht übersichtlich:

public class Kiosk {

    private Queue<Schueler> sekEins;
    private Queue<Schueler> sekZwei;
    private String message;

    public Kiosk() {
        sekEins = new Queue<Schueler>();
        sekZwei = new Queue<Schueler>();
        message = "Der Kiosk wurde geöffnet!";
    }

    // Hier kommt gleich noch mehr hin!
    
    public String getMessage() {
        return message;
    }

}

Wir sehen hier die beiden Queues, die im Konstruktor zunächst einmal erzeugt werden. Beide sind anfangs leer. Außerdem verwenden wir einen String, der stets die letzte Meldung des Kiosks speichern soll.

Die Methode zum Anstellen eines neuen Schülers könnten wir so umsetzen:

public void stelleAn(Schueler pNeuer) {
    message = "Es hat sich angestellt: " + pNeuer.getName();
    if (pNeuer.getObertufe()) {
        sekZwei.enqueue(pNeuer);
        message = message + " Oberstufe";
    } else {
        sekEins.enqueue(pNeuer);
        message = message + " Mittelstufe";
    }
}

Je nachdem, ob der neue Schüler in der Oberstufe ist oder nicht, kommt er in die entsprechende Queue.

Kommen wir jetzt zum Bedienen:

public void bedieneErsten() {
    if (!sekZwei.isEmpty()) {
        message = "Es wird nun bedient: " + sekZwei.front().getName() + " Oberstufe";
        sekZwei.dequeue();
    } else if (!sekEins.isEmpty()) {
        message = "Es wird nun bedient: " + sekEins.front().getName() + " Mittelstufe";
        sekEins.dequeue();
    } else {
        message = "Es steht niemand an!";
    }
}

Zur Übung empfiehlt es sich, diese Methode eigenständig zu analysieren 🙂

Es fehlt noch die Methode pausengong(). Diese will ich hier nicht vorwegnehmen, sondern möchte sie auch als Übung offen lassen.

Natürlich sollten wir unsere Klasse Kiosk noch ausgiebig testen. Ein erster Test könnte so aussehen:

public class KioskTest {

    public static void main(String[] args) {

        Kiosk meinKiosk = new Kiosk();
        meinKiosk.stelleAn(new Schueler("Anton", true));
        System.out.println(meinKiosk.getMessage());

        meinKiosk.stelleAn(new Schueler("Berta", false));
        System.out.println(meinKiosk.getMessage());

        meinKiosk.stelleAn(new Schueler("Caesar", false));
        System.out.println(meinKiosk.getMessage());

        meinKiosk.stelleAn(new Schueler("Dora", true));
        System.out.println(meinKiosk.getMessage());

        meinKiosk.bedieneErsten();
        System.out.println(meinKiosk.getMessage());

        meinKiosk.pausengong();
        System.out.println(meinKiosk.getMessage());

        meinKiosk.bedieneErsten();
        System.out.println(meinKiosk.getMessage());

    }

}

Wurde alles korrekt umgesetzt, sollte die Ausgabe in der Konsole so (oder so ähnlich) aussehen:

Es hat sich angestellt: Anton Oberstufe
Es hat sich angestellt: Berta Mittelstufe
Es hat sich angestellt: Caesar Mittelstufe
Es hat sich angestellt: Dora Oberstufe
Es wird nun bedient: Anton Oberstufe
Pausengong! Die Sek. I verlässt die Schlange.
Es wird nun bedient: Dora Oberstufe

Für ein vollständiges Projekt benötigen wir noch eine Dokumentation. Als Implementationsdiagramm ist das Diagramm oben schon geeignet. Warum wir Queues benutzt haben, haben wir oben auch schon erklärt. Es fehlt noch die genaue Beschreibung dessen, was die verschiedenen Methoden unserer Klassen leisten. Eine solche sollte vom Stil her aussehen wie die Dokumentationen der Abiturklassen.
Das Erstellen will ich ebenfalls als Übung hier offen lassen!

Vielleicht fragt sich der ein oder andere, warum in der Klasse Kiosk das Attribut message verwendet wird. Das scheint doch etwas umständlicher als einfach direkt eine System.out.print-Anweisung, oder?
Ich benutze es aus verschiedenen Gründen. Auf einige gehe ich nur in meinem eigenen Unterricht ein (wie verwenden dort eine eigene Bibliothek durch die die Verwendung verständlicher wird.)
Ein recht netter Grund ist aber, dass besonders Engagierte Benutzeroberfläche mit JavaFX (oder auf dem im Unterricht sonst verwendeten Weg) erstellen können:

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.FlowPane;
import javafx.scene.control.CheckBox;
import static javafx.application.Application.launch;

public class KioskMainFX extends Application {

    Kiosk meinKiosk;

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage meineStage) throws Exception {

        meinKiosk = new Kiosk();

        // Titel
        meineStage.setTitle("Kiosk FX");

        // Erzeugen der GUI-Komponenten
        final Button btnAnstellen = new Button("Anstellen");
        final Button btnBedienen = new Button("Bedienen");
        final Button btnPausengong = new Button("Gong");
        final CheckBox chkOberstufe = new CheckBox("Oberstufe");
        final TextField txtName = new TextField();
        txtName.setPromptText("Namen bitte eingeben!");
        final Label lblMessage = new Label("Willkommen beim Kiosk!");

        // Erzeugen des Layouts und Platzieren der Komponenten
        final FlowPane root = new FlowPane();

        // Einzelne Komponente hinzufügen:
        root.getChildren().add(txtName);
        root.getChildren().add(chkOberstufe);
        root.getChildren().add(btnAnstellen);
        root.getChildren().add(btnBedienen);
        root.getChildren().add(btnPausengong);
        root.getChildren().add(lblMessage);

        // Abstaende zwischen den Komponenten
        root.setHgap(30);
        root.setVgap(20);

        // Festlegen der Szene
        final Scene meineSzene = new Scene(root, 340, 120);
        meineStage.setScene(meineSzene);
        meineStage.show();

        btnAnstellen.addEventHandler(ActionEvent.ACTION,
                new EventHandler<ActionEvent>() {
            public void handle(ActionEvent meinEvent) {

                meinKiosk.stelleAn(new Schueler(txtName.getText(), chkOberstufe.isSelected()));
                lblMessage.setText(meinKiosk.getMessage());

            }
        });

        btnBedienen.addEventHandler(ActionEvent.ACTION,
                new EventHandler<ActionEvent>() {
            public void handle(ActionEvent meinEvent) {

                meinKiosk.bedieneErsten();
                lblMessage.setText(meinKiosk.getMessage());
            }
        });

        btnPausengong.addEventHandler(ActionEvent.ACTION,
                new EventHandler<ActionEvent>() {
            public void handle(ActionEvent meinEvent) {

                meinKiosk.pausengong();
                lblMessage.setText(meinKiosk.getMessage());
            }
        });

    }

}

Nun könnte man – unabhängig von JavaFX – das Projekt noch komplexer gestalten. Vielleicht möchte man dem Konstruktor der Klasse Kiosk einen weiteren Parameter übergeben, mit dem man festlegen kann, ob tatsächlich die älteren oder nicht doch die jüngeren Schüler drängeln. Oder vielleicht hat jeder Schüler einen Artikel im Sinn, den er kaufen möchte. Damit könnte man die Einnahmen des Kiosks berechnen. Mit etwas Phantasie sind noch viele weitere Szenarien denkbar.
Probiert einfach was aus 🙂