CS_LEVEL_15_SOLUTION - OnlyCook/abitur-elite-code GitHub Wiki

Level 15 – Musterlösung: Protokoll-Parser (Switch)

Lösung

public class Kontrollzentrum
{
    private DateTime letzterScan;
    private Rover rover;
 
    public Kontrollzentrum()
    {
        this.rover = new Rover("Curiosity");
    }
 
    public void VerarbeiteKommando(string befehl)
    {
        string[] parts = befehl.Split(';');
 
        switch (parts[0])
        {
            case "MV":
                rover.Move(int.Parse(parts[1]), int.Parse(parts[2]));
                break;
            case "TR":
                rover.Turn(parts[1]);
                break;
            case "SC":
                letzterScan = rover.Scan(parts[1]);
                break;
        }
    }
}
 
public class Rover
{
    private string id;
    private int[] position;
    private string ausrichtung;
 
    public Rover(string id)
    {
        this.id = id;
        this.position = new int[2];
    }
 
    public void Move(int x, int y)
    {
        position[0] = x;
        position[1] = y;
    }
 
    public void Turn(string richtung)
    {
        ausrichtung = richtung;
    }
 
    public DateTime Scan(string ziel)
    {
        return DateTime.Now;
    }
}

Erklärung

Willkommen in Sektion 4

Ab jetzt geht es um Kommunikation & Protokolle – ein zentrales Thema im Abitur. Statt Daten einfach direkt zu übergeben, werden sie als formatierte Strings codiert und wieder zerlegt. Das Kontrollzentrum in diesem Level empfängt solche Strings und leitet die richtigen Aktionen an den Rover weiter.


Die Klasse Rover

Rover ist eine klassische Datenklasse mit einem Konstruktor und ein paar Methoden – hier gibt es strukturell nichts Neues. Wichtig ist das Attribut position:

private int[] position;

Ein Array ist eine feste Sammlung von Werten eines Typs. int[2] bedeutet: zwei Ganzzahlen, Index 0 und Index 1 – also genau das, was wir für x und y brauchen. Im Konstruktor wird das Array initialisiert:

this.position = new int[2];

In Move() werden die Koordinaten dann direkt in die beiden Stellen des Arrays geschrieben:

position[0] = x;
position[1] = y;

Scan() gibt einfach DateTime.Now zurück – der aktuelle Zeitstempel des Scans.


Strings zerlegen mit Split()

Das Herzstück dieses Levels ist das Parsen des Befehlsstrings. Ein Befehl sieht z.B. so aus:

"MV;50;90"

Mit Split(';') wird der String am Semikolon aufgeteilt und als string[] zurückgegeben:

string[] parts = befehl.Split(';');
// parts[0] = "MV"
// parts[1] = "50"
// parts[2] = "90"

parts[0] enthält immer den Befehlstyp – danach kommen die Parameter. Dabei gilt: Alles ist erst mal ein String. Wenn man Zahlen braucht, muss man sie explizit umwandeln:

int.Parse(parts[1])  // "50" -> 50

Die switch-Anweisung

Statt mehrerer if / else if-Blöcke bietet sich hier eine switch-Anweisung an – besonders dann, wenn man einen Wert gegen mehrere feste Möglichkeiten prüft:

switch (parts[0])
{
    case "MV":
        rover.Move(int.Parse(parts[1]), int.Parse(parts[2]));
        break;
    case "TR":
        rover.Turn(parts[1]);
        break;
    case "SC":
        letzterScan = rover.Scan(parts[1]);
        break;
}

Jeder case-Block entspricht einem Befehlstyp. Das break am Ende ist Pflicht – es beendet den Block und verhindert, dass der nächste case ebenfalls ausgeführt wird (sog. Fall-through). Ohne break würde C# einen Compilerfehler werfen.

Für den "SC"-Fall wird der Rückgabewert von rover.Scan() direkt im Attribut letzterScan gespeichert – da Scan() einen DateTime zurückgibt, muss letzterScan auch vom Typ DateTime sein.


Der Konstruktor von Kontrollzentrum

Im Konstruktor wird der Rover direkt erzeugt und dem Attribut zugewiesen:

public Kontrollzentrum()
{
    this.rover = new Rover("Curiosity");
}

Das Kontrollzentrum „besitzt" seinen Rover – er wird nicht von außen übergeben, sondern intern erstellt. Das ist eine bewusste Design-Entscheidung, die das Klassendiagramm vorgibt (1-zu-1-Assoziation ohne externe Übergabe im Konstruktor).