Input/Output Implementierung eines CommandHandlers/Parsers für viele Eingaben

DagobertDuck

Bekanntes Mitglied
Hi,

ich arbeite momentan an einem Projekt zur Eisenbahnsimulierung und bin mir bei der Implementierung der Benutzerschnittstelle noch nicht sicher.
Bisher war meine Idee, ein Interface namens Command zu erstellen und dann für jeden Befehl eine eigene Klasse. Wäre es nicht eventuell sogar besser jeweils eine Klasse für alle Add Befehle (add track, add train, ...), für alle List Befehle (list tracks, list engines, ...) zu erstellen, oder für alle Befehle, die sich auf ein coach (Wagen) beziehen also z. B. CoachCommands (create coach, list coaches, ...)?

Könnte mir jemand eine sinnvolle Klassen bzw. Implementierungsstruktur geben? Was mich außerdem verunsichert, sind die Anzahl Wörter der Befehle. Beispielsweise gibt es Befehle, die aus zwei Wörtern bestehen sollen (add track, delete track, list tracks, ...) und wiederum gibt es Befehle, die nur aus einem Wort bestehen (step, exit, ...). Ich zerschlage mir schon den ganzen Tag den Kopf darüber und bastel mir Codeschnipsel zusammen, wie ich dies clever implementieren kann. Leider bisher ohne wirklichen Erfolg.

Hier ist die Liste aller Befehle, die ich implementieren möchte:
- add track <startpoint> -> <endpoint>
- delete track <trackID>
- list tracks
- set switch <trackID> position <point>
- create engine <engineType> <class> <name> <length> <couplingFront> <couplingBack>
- list engines
- create coach <coachType> <length> <couplingFront> <couplingBack>
- list coaches
- create train-set <class> <name> <length> <couplingFront> <couplingBack>
- list train-sets
- delete rolling stock <id>
- add train <trainID> <rollingStockID>
- list trains
- show train <trainID>
- put train <trainID> at <point> in direction <x>,<y>
- step <speed>
- exit
 

mihe7

Top Contributor
Zum Beispiel:
Java:
interface Command {
    public void execute(List<String> arguments);
}
Dann ein paar Helferlein
Java:
public class UnknownCommandException extends RuntimeException {
    private String token;

    public UnknownCommandException(String token) {
        super("Unbekannter Befehl " + token);
        this.token = token;
    }

    public String getToken() { return token; }
}
und
Java:
class DefaultCommand implements Command {
    private Map<String, Command> commands = new HashMap<>();

    public void register(String name, Command command) {
        commands.put(name, command);
    }

    @Override
    public void execute(List<String> arguments) {
        if (arguments.isEmpty()) { return; }

        Command cmd = commands.get(arguments.get(0));
        if (cmd != null) {
            cmd.execute(arguments.subList(1, arguments.size()));
        } else {
            throw new UnknownCommandException();
        }
    }    
}
Jetzt brauchen wir noch die Befehlszeile
Java:
class CommandLine {
    private final Command command;

    public CommandLine(Command root) {
        command = root;
    }

    public void execute(String line) {
        command.execute(Arrays.asList(line.split("\\s")));
    }
}
Und was zum Testen:
Java:
public class Test {
    private CommandLine cli;

    public Test() {
        DefaultCommand add = new DefaultCommand();
        add.register("track", args -> System.out.println("add track: " + args));
        add.register("train", args -> System.out.println("add train: " + args));

        DefaultCommand delete = new DefaultCommand();
        delete.register("track", args -> System.out.println("delete track: " + args));
        delete.register("train", args -> System.out.println("delete train: " + args));
        DefaultCommand root = new DefaultCommand();
        root.register("add", add);
        root.register("delete", delete);

        cli = new CommandLine(root);
    }

    public void execute(String line) {
        cli.execute(line);
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.execute("add track a b c");
        test.execute("add train xy");
        test.execute("delete track 1");
        test.execute("xy");
    }
}
 

CSHW89

Bekanntes Mitglied
Seit Java 8 implementiere ich das Command-Pattern eher mit einem einzigen Funktionalen Interface und Lambdaausdrücke für jedes einzelne Command. Ggf. können die Command-Factory-Methoden in ein Controller umgelagert werden.
Java:
interface Command {
    public void execute();
    
    public static Command addTrack(Point start, Point end) {
        return () -> getTrackList().add(new Track(start, end));
    }
    public static Command removeTrack(Track track) {
        return () -> getTrackList().remove(track);
    }
    ...
}
 

DagobertDuck

Bekanntes Mitglied
Vielen Dank für die Hilfe @mihe7 und @CSHW89. Das hilft mir bereits enorm bei dem Verständnis der Implementierung..
Jedoch bin ich mir immer noch nicht ganz sicher wie ich nun weiter machen soll.
Ist für jeden Command eine eigene Klasse sinnvoll und wie würde ich das dann konkret in den Vorschlag von @mihe7 einarbeiten? Könnte mir jemand dies anhand eines beliebigen Befehls verdeutlichen?
 

DagobertDuck

Bekanntes Mitglied
@mihe7 Könntest du deine Antwort noch etwas ausführen, indem du dein Command-Pattern konkret auf einen meiner Befehle anwendest? Ich wäre dir sehr dankbar, da ich noch nicht ganz verstanden habe, wie ich alle Befehle genau implementieren soll.
 

mihe7

Top Contributor
indem du dein Command-Pattern konkret auf einen meiner Befehle anwendest?
Wie meinst Du das? Im Code sind bereits vier Befehle implementiert.

Die Implementierung des Command-Interfaces erfolgt einmal über die Klasse DefaultCommand (das ist ein Befehl, bei dem Unterbefehle registriert werden können) und einmal in Form von Lambda-Ausdrücken.

Das hier
Java:
add.register("track", args -> System.out.println("add track: " + args));
ist sozusagen die Kurzschreibweise für:
Java:
add.register("track", new Command() {
    @Override
    public void execute(List<String> args) {
        System.out.println("add track: " + args);
    }
});
wobei die anonyme innere Klasse eben den "add track"-Befehl implementiert.

Die Implementierung von Command hat nur die Aufgabe, die gegebenen Parameter in einen geeigneten Methodenaufruf umzusetzen. Dabei können (bzw. sollten) die "Syntax" überprüft und Parameter konvertiert werden.

Nehmen wir mal den Befehl "add track <startpoint> -> <endpoint>", dann würde die innere Klasse oben als Parameter-Liste ["<startpoint>", "->", "<endpoint>"] erhalten.

D. h. der Befehl "add track" erwartet die Parameter in der angegebenen Form. Außerdem nehmen wir mal an, dass startpoint und endpoint ganzzahlige Werte (int) sein sollen. Dann könntest Du schemenhaft schreiben:
Java:
public void execute(List<String> args) {
    if (args.size() != 3) { throw new IllegalArgumentException("falsche Anzahl an Parametern"); }
    if (!"->".equals(args.get(1))) { throw new IllegalArgumentException("zweiter Parameter muss '->' sein, war aber " + args.get(1)); }

    String startpoint = args.get(0);
    String endpoint = args.get(2);
    int start = Integer.parseInt(startpoint);
    int end = Integer.parseInt(endpoint);

    tracks.add(start, end); // oder was auch immer
}
 

DagobertDuck

Bekanntes Mitglied
Super, danke schön! Ich war noch so mit meiner ursprünglichen Implementierungs-Idee in Gedanken, dass ich gar nicht richtig bemerkt habe, dass deine Struktur alle Commands in einer Klasse hinzufügt (inklusiv der execute() Methoden der jeweiligen Befehle).
 

DagobertDuck

Bekanntes Mitglied
Das Problem der Benutzerschnittstelle habe ich bereits gelöst. Ich habe mihe7's Code etwas angepasst und für jeden Command eine eigene Klasse erstellt. Im Java-Anfänger Forum habe ich eine Frage zum Regex gestellt, eventuell könntest du mir dabei weiterhelfen.
 

DagobertDuck

Bekanntes Mitglied
@mihe7 Wie könnte man am einfachsten für deinen Code eine Fehlermeldung ausgeben, wenn z. B. nur "add" eingegeben wird. "quit abcdefg" funktioniert z. B. auch, obwohl nur "quit" ohne weitere Argumente angenommen werden soll. Für "add track" wird bereits die Fehlermeldung "falsche Anzahl an Argumenten" ausgegeben, da du eine Abfrage in der execute() Methode hinzugefügt hast.
 

DagobertDuck

Bekanntes Mitglied
@mihe7 Super, danke. Jetzt habe ich alles dementsprechend angepasst.
Der Übersicht halber habe ich alle Commands außer den "quit" Command in eigene Klassen ausgelagert.
Hast du eine Idee, wie ich auch den "quit" Command in eine eigene Klasse auslagern könnte?

Hier ist ein Ausschnitt meiner Main Klasse, sodass du verstehst, wie sie in etwa funktioniert:
Java:
while (running) {

        ...   

        test.execute(input);

        ...
     
    }

}

und hier ist mein derzeitiger "quit" Command:
Java:
root.register("quit", new DefaultCommand() {

    @Override

    public void execute(List<String> args) throws BadSyntaxException {

        if (!args.isEmpty()) { throw new BadSyntaxException("unknown command"); }

        running = false;

    }

});

Kann ich irgendwie in einer separaten Klasse den boolean running auf false setzen? Ich fände es schöner, wenn alle Befehle (inklusive des quit-Befehls) in einer eigenen Klasse sind.
 

mihe7

Top Contributor
Sicher, Du brauchst für das boolean nur ein veränderliches Objekt statt des primitiven Typs zu verwenden, z. B.
Java:
private final AtomicBoolean running = new AtomicBoolean(true);

// irgendwo
root.register("quit", args -> running.set(false));

// die loop
public void run() {
    while (runining.get()) {
        ...
    }
}
 

DagobertDuck

Bekanntes Mitglied
@mihe7 Des Weiteren habe ich noch ein Problem mit dem Command
delete.register("rolling stock", new DeleteRollingStockCommand(register));. Da hier ein whitespace vorhanden ist, wird immer "unknown command" zurückgegeben. Jetzt möchte ich allerdings nicht noch einen Unterbefehl erstellen, also den rolling stock Befehl in "rolling" und in "stock" aufteilen. Hast du eine Idee, wie ich dies einfach lösen kann?
 

mihe7

Top Contributor
Gibt es eine Alternative?
Schreib einfach eine ähnliche Klasse.

Da hier ein whitespace vorhanden ist, wird immer "unknown command" zurückgegeben. Jetzt möchte ich allerdings nicht noch einen Unterbefehl erstellen, also den rolling stock Befehl in "rolling" und in "stock" aufteilen. Hast du eine Idee, wie ich dies einfach lösen kann?
rolling ist ein DefaultCommand, bei dem Du den Unterbefehl stock registrierst.

Das gleiche gilt für die Befehle "show train" und "put train".
So ist es.
 

DagobertDuck

Bekanntes Mitglied
@mihe7 In dem Sinne ist „stock“ kein Unterbefehl und macht nur in Verbindung mit „rolling“ Sinn, insgesamt also „rolling stock“. Rolling stock = Rollmaterial (~Zugteile). Wenn es hierfür keine einfache Lösung gibt werde ich das allerdings so machen.
 

mrBrown

Super-Moderator
Mitarbeiter
@mihe7 In dem Sinne ist „stock“ kein Unterbefehl und macht nur in Verbindung mit „rolling“ Sinn, insgesamt also „rolling stock“. Rolling stock = Rollmaterial (~Zugteile). Wenn es hierfür keine einfache Lösung gibt werde ich das allerdings so machen.
Die einfachste Lösung wäre wohl ein Bindestrich statt Leerzeichen, wie auch bei train-set/train-sets
 

mihe7

Top Contributor
In dem Sinne ist „stock“ kein Unterbefehl
Das spielt keine Rolle. Im Modell kann ein Befehl per Definition keine Leerzeichen enthalten. Die Frage ist also nur, ob das Modell geeignet ist, Deine Befehle abzubilden. Und das funktioniert für die genannten Fälle (noch). Tatsächlich ist das hier auch eher eine Frage der Begrifflichkeit: verwende statt "Unterbefehl" einfach "Teil eines Befehls" :)
 

DagobertDuck

Bekanntes Mitglied
Sicher, Du brauchst für das boolean nur ein veränderliches Objekt statt des primitiven Typs zu verwenden, z. B.
Java:
private final AtomicBoolean running = new AtomicBoolean(true);

// irgendwo
root.register("quit", args -> running.set(false));

// die loop
public void run() {
    while (runining.get()) {
        ...
    }
}

So sieht jetzt meine Implementierung bisher aus:

Main.java:
Java:
public class Main {

    public static void main(String[] args) {

        Session session = new Session();

        session.run();

    }

}

Session.java:
Java:
public class Session {

    protected boolean running = true;



    /**

     * After starting the session this method remains in a loop until the {@link this#terminate()}

     * method is called.

     */

    void run() {

        RailRegister register = new RailRegister();



        while (running) {

            String input = Terminal.readLine();

            try {

                UserInterface userInterface = new UserInterface(register);

                userInterface.execute(input);

            } catch (UnknownCommandException | BadSyntaxException e) {

                Terminal.printError(e.getMessage());

            }

        }

    }

    /**

     * Terminates the session.

     */

    public void terminate() {

        running = false;

    }

}

QuitCommand.java:
Java:
public class QuitCommand extends AbstractCommand {
    public QuitCommand(RailRegister register) {
        super(register);
    }

    @Override
    public void execute(List<String> arguments) throws BadSyntaxException {
        // TODO: Execute terminate() method
    }
}

Jedoch weiß ich noch nicht, wie ich in QuitCommand.java auf die terminate() Methode bzw. den running boolean zugreifen soll.
 

mrBrown

Super-Moderator
Mitarbeiter
@mrBrown In diesem Fall leider schon :confused:.
Der „Hinweis“ kam deshalb, weil ich mir gut vorstellen kann, dass es einfach ein Fehler in der Aufgabenstellung/ein nicht bedachter Edge-Case ist, der eigentlich gar nicht so sein sollte - grad auch weil dieser Befehl sich in der Schreib-Art von allen anderen unterscheidet. Solche Fehler passieren häufig, und fallen einfach niemandem auf, wenn niemand nachfragt.
(Nur meine Meinung als der, der dabei meist auf der Aufgabensteller-Seite sitzt)
 
Ähnliche Java Themen
  Titel Forum Antworten Datum
Stonie Prüfen von direkter Implementierung eines Interfaces Allgemeine Java-Themen 7
L Implementierung eines AVT-Baums Allgemeine Java-Themen 2
A Implementierung eines Algorithmus (Farthest Insertion zur Lösung des TSP) in O(n²) Allgemeine Java-Themen 2
R Implementierung eines Interface durch 2 verschiedene Klassen Allgemeine Java-Themen 6
K Objekt einer konkreten Implementierung eines Interfaces durch übergebenen String Allgemeine Java-Themen 2
B Elegantere Lösung bei der Implementierung eines Interfaces Allgemeine Java-Themen 2
L Unterschied zwischen List und LinkedList implementierung? Allgemeine Java-Themen 15
boschl2000 Springerproblem-Implementierung funktioniert nicht richtig Allgemeine Java-Themen 1
L rotateLeft implementierung Allgemeine Java-Themen 2
R In der Ausgabe sollte anstelle des obersten Sterns ein "+" stehen nur scheitere ich bei der Implementierung Allgemeine Java-Themen 9
S Mutable objects und Implementierung von ChangeEvents Allgemeine Java-Themen 5
W Queue Implementierung Allgemeine Java-Themen 6
C Ein Iterator ist eine Implementierung des Interface Iterable? Allgemeine Java-Themen 2
F Implementierung von Teilprogrammen [Java|Python] Allgemeine Java-Themen 7
I TimSort - Sortieralgorithmus - Erklärung und Pseudocode - Implementierung Allgemeine Java-Themen 2
ruutaiokwu burstsort-implementierung in java? Allgemeine Java-Themen 2
D Implementierung einer Mehrsprachigkeit, wichtig ? Allgemeine Java-Themen 5
D Implementierung einer Rechteverwaltung Allgemeine Java-Themen 2
R "Countdown" Implementierung Allgemeine Java-Themen 5
K A*-Implementierung flexibler machen Allgemeine Java-Themen 4
J Java-Implementierung diverser Beziehungen zwischen Klassen bzw. Objekten Allgemeine Java-Themen 2
S BlueJ Cäsar-Implementierung Allgemeine Java-Themen 6
S Implementierung Programmneustart Allgemeine Java-Themen 10
G Implementierung einer Kommunikation Allgemeine Java-Themen 7
S Implementierung einer PluginArchitektur Allgemeine Java-Themen 5
A OOP: Überschreiben/Implementierung von Methoden Allgemeine Java-Themen 5
R Intervall-Implementierung mit selbstgebauter LinkedList Allgemeine Java-Themen 7
J Best Practice für implementierung von equals(...) Allgemeine Java-Themen 7
Kr0e Eigene RMI Implementierung Allgemeine Java-Themen 3
V Wie finde ich die konkrete Implementierung? Allgemeine Java-Themen 8
G Implementierung vom AKS-Test Allgemeine Java-Themen 11
R software implementierung Allgemeine Java-Themen 3
N Observer/Observable der JAVA-API od. eigene Implementierung Allgemeine Java-Themen 2
K Design / Implementierung Allgemeine Java-Themen 5
B jre browser implementierung ? Allgemeine Java-Themen 4
G Klasse Queue Implementierung in Java Allgemeine Java-Themen 4
G Eigene PrintService Implementierung. Allgemeine Java-Themen 5
O regulärer Ausdruck zum durchsuchen eines Strings verwenden Allgemeine Java-Themen 2
T Rotationswinkel eines Bildes bestimmen Allgemeine Java-Themen 4
C Probleme beim Erstellen eines runnable-jar files Allgemeine Java-Themen 1
J JavaScript innerhalb eines Java Projekts ausführen Allgemeine Java-Themen 2
Encera Größe eines Objektes in Byte berechnen Allgemeine Java-Themen 2
8u3631984 Prüfen ob min. ein Element eines Sets in einem anderen Set enh Allgemeine Java-Themen 4
M Array Rang eines Elements Allgemeine Java-Themen 4
OnDemand Teile eines Links entfernen Allgemeine Java-Themen 6
H Auslesen eines (LDAP-)Attributs in Active Directory Allgemeine Java-Themen 2
W JSON parsen eines ,mit JS.stringify erstellten Strings Allgemeine Java-Themen 27
H Textposition eines gedrehten Textes verschieben Allgemeine Java-Themen 8
berserkerdq2 run-methode eines Threads so programmieren, dass 30x die Sekunde etwas ausgeführt wird. Allgemeine Java-Themen 44
E Ersetzen eines Bildes in der Kopfzeile eines Word-Docx-Dokuments mit Apache POI XWPF Allgemeine Java-Themen 0
N Fahrtrichtung eines selbstfahrenden Auto ändern Allgemeine Java-Themen 3
T Letztes Zeichen eines Strings enfernen Allgemeine Java-Themen 14
S Übergabe eines Sortierkriteriums für ein Artikel Array mittels BiPredicate<Artikel, Artikel> Allgemeine Java-Themen 13
gotzi242 Schatzsuche mithilfe eines O(log n) Algorithmus Allgemeine Java-Themen 2
C Koordinaten LONG/LAT eines neuen Punktes in bestimmter Entfernen und Winkel berechnen Allgemeine Java-Themen 3
Tobero Meine Funktion für das beinhalten eines Punktes in einem Kreis funktioniert nicht Allgemeine Java-Themen 5
LimDul Direktes return eines Array geht nicht Allgemeine Java-Themen 20
S Mittelwert anhand eines Stream berechnen Allgemeine Java-Themen 5
kodela Breite eines erweiterten Monitors feststellen Allgemeine Java-Themen 5
R Zeilen eines 2d Arrays abwechselnd links und rechts mit Nullen auffüllen Allgemeine Java-Themen 14
Zrebna Alternative Darstellung eines Codesnippets Allgemeine Java-Themen 33
kodela Inhalt eines Arrays ändert sich mysteriös Allgemeine Java-Themen 2
bueseb84 Wget mit Wildcards - oder wie lädt man bei JFrog die letzte Version eines Artifacts herunter Allgemeine Java-Themen 3
N Erkennen eines Programs Allgemeine Java-Themen 2
N Pausieren eines Programmes Allgemeine Java-Themen 4
M Gibt es eine API die den aktuellen Wert eines Indikators beim Trading zurückgibt? Allgemeine Java-Themen 7
F Wie bekommt man alle Filenamen eines Webserver Verzeichnisses Allgemeine Java-Themen 6
A Fehler beim Öffnen eines Projekts Allgemeine Java-Themen 6
N Eigenschaften eines Buttons per Setter verändern Allgemeine Java-Themen 5
S Ausfuehrung eines Programms aufzeichnen..? Allgemeine Java-Themen 4
X Ermittlung eines doppelte Paars mit Streams Allgemeine Java-Themen 50
S Vorbereitung eines Praktikums Allgemeine Java-Themen 4
H Aufruf eines Web Service anhand übergebenen Parameter Allgemeine Java-Themen 2
M Weiterleiten von empfangenen Nachrichten eines StompSessionHandlers Allgemeine Java-Themen 1
J Programm zum Suchen eines Wortes im Dateisystem Allgemeine Java-Themen 4
H Rename eines Projekts Allgemeine Java-Themen 1
J Fenstergröße eines anderen Programmes auslesen Allgemeine Java-Themen 9
ReinerCoder auf Klassen innerhalb eines package zugreifen Allgemeine Java-Themen 22
Meeresgott Erste Schritte Sourcetree - Git | Suchen eines Commits Allgemeine Java-Themen 2
E Status eines USB Mikrofon abfragen Allgemeine Java-Themen 2
DaCrazyJavaExpert OOP Ansätze und Tipps zum Porgrammieren eines Taschenrechners Allgemeine Java-Themen 25
A OOP Problem beim Berechnen der größten Fläche eines Ringes Allgemeine Java-Themen 19
JavaNewbie2.0 Start eines Anderen Programm erkennen Allgemeine Java-Themen 6
I Verbindung eines Java-Plugins mit Webserver Allgemeine Java-Themen 3
L Auswertung eines Testes funktioniert nicht Allgemeine Java-Themen 37
G Iteratoren - Wie kann man mithilfe von Iteratoren nur jeden zweiten Wert eines TreeSets ausgeben? Allgemeine Java-Themen 4
GreenTeaYT Elemente eines 2Dim LinkedList von links nach rechts ausgeben? Allgemeine Java-Themen 0
B Spalten eines 2d-Arrays Allgemeine Java-Themen 2
M Rechenprogramm eines wissenschaftlichen Taschenrechners Allgemeine Java-Themen 4
S Eigenschaften (hier Verknüpfung) eines Files lesen Allgemeine Java-Themen 2
E Typüberprüfung eines chars Allgemeine Java-Themen 5
H Hilfe bei Erstellung eines Hilfe Fenster bei Tastendruck (F1 bei Win98) Allgemeine Java-Themen 5
T Teile eines Double-Wertes verändern Allgemeine Java-Themen 2
R Rückgabe eines Arrays durch Funktion Allgemeine Java-Themen 9
H Datentypen Typ eines Arrays überprüfen Allgemeine Java-Themen 9
RalleYTN DPI eines Bildes ändern Allgemeine Java-Themen 4
N Methoden Methoden einer Klasse auf Grundlage eines Strings aufrufen Allgemeine Java-Themen 6
K Bestimmten Bereich eines Strings lesen Allgemeine Java-Themen 6
C -Verschiedene Versionen eines Programms verwalten Allgemeine Java-Themen 7
O Datentypen Erstellung eines Containers, der verschachtelte Map-Strukturen beherbergen kann Allgemeine Java-Themen 0

Ähnliche Java Themen

Neue Themen


Oben