12.5.5 OOP - Züge


Für ein Eisenbahnnetz sollen Züge mit einer geeigneten Zugverwaltungssoftware verwaltet werden. Ein Zug besteht aus einem Triebwagen und dahinter angekoppelten Waggons. Die unterschiedlichen Waggontypen sind:

  • Reisezugwaggon (für den Personentransport)
  • Speisewaggon (für den kleinen Snack zwischendurch)
  • Güterwaggon (für den Gütertransport)
  • Bahndienstwaggon (für den internen Gebrauch, wie Gleisreparatur,...)

Die Programmierung der Verwaltungssoftware soll objektorientiert erfolgen. Dazu soll es eine allgemeine Klasse "Waggon" geben und die speziellen Waggontypen sollen von der allgemeinen Klasse erben. Ein mögliches Klassendiagramm könnte wie folgt aussehen:

Hinweis zum Klassendiagramm:

  • Eine Beziehung zwischen zwei Klassen, die mit einem Pfeil symbolisiert wird, der ein Rautensymbol am Ende hat, wird Aggregation genannt und bedeutet, dass die eine Klasse als ein Bestandteil in der anderen Klasse vorkommt.
  • Eine Beziehung zwischen zwei Klassen, die mit einem Pfeil symbolisiert wird, der ein Dreieck am Ende hat, wird Vererbung genannt und bedeutet, dass die eine Klasse Attribute und Operationen von der anderen Klasse erbt. Das Schlüsselwort dafür ist "extends".

Jedem Zugwagen soll ein Lokomotivführer mit Namen und Personalnummer zugeordnet werden können. Ausserdem sollen die Güterwaggons weiter unterteilt werden:

  • Kesselwaggons (für Flüssigkeiten oder Gase, auch Gefahrgüter)
  • Kühlwaggons (mit regelbarer Temperatur)
  • Waggons mit öffnungsfähigem Dach (Schiebedach, Schwenkdach, Rolldach)

Dazu wird das Klassendiagramm weiter entwickelt:

Auf der Basis eines solchen Klassendiagramms kann die Softwareentwicklung starten. Dabei sollen die realen Objekte auf Objekte in der Software abgebildet werden.

1.: Starten Sie die Software Drawio. Legen Sie ein neues Dokument an, wählen Sie dazu die Vorlage "Software" und klicken Sie darin das erste Template an.

2.: Bauen Sie in Drawio das erweiterte Klassendiagramm für das Zugprojekt nach.

In diesem Abschnitt soll das Klassendiagramm in ein lauffähiges Programm umgesetzt werden. Dazu werden die Klassen des Diagramms als JavaScript-Klassen programmiert:

1) Implementierung von Klasse "Zug"

class zugKlasse {
  constructor(triebWagen, waggonArray) {
    this.triebWagen = triebWagen;
    this.waggonArray = waggonArray;
    this.korrekt = false;
  }
  zugKorrekt() {
    if((this.triebWagen != undefined) && (this.triebWagen != null) && (this.waggon != undefined) && (this.waggon != null)) {
      this.korrekt = true;
    } else {
      this.korrekt = false;  
    }
  } 
}    

2) Implementierung von Klasse "Triebwagen"

class triebwagenKlasse {
  constructor(lokomotivFuehrer, triebwagenTyp, triebwagenNummer) {
    this.lokomotivFuehrer = lokomotivFuehrer;
    this.triebwagenTyp = triebwagenTyp;
    this.triebwagenNummer = triebwagenNummer;
    this.nummer = 0;
  }
  setzeNummer(Nr) {
        this.nummer = Nr;
  }
} 

3) Implementierung von Klasse "Lokomotivführer"

class lokomotivfuehrerKlasse {
  constructor(name, personalnummer) {
    this.name = name;
    this.personalnummer = personalnummer;
  }
} 

4) Implementierung von Klasse "Waggon"

class waggonKlasse {
  constructor(waggonTyp, waggonNummer) {
    this.waggonTyp = waggonTyp;
    this.waggonNummer = waggonNummer;
  }
  setzeNummer(Nr) {
        this.nummer = Nr;
  }       
} 

5) Implementierung von Klasse "Güterwaggon". Die Klasse "Güterwaggon" erweitert die Klasse "Waggon".

Die Werte der Attribute "waggonTyp" und "waggonNummer" werden an die übergeordnete Klasse mit Hilfe der Funktion "super()" weitergegeben. Die Werte der Attribute "ladungsart" und "volumen" werden in der untergeordneten Klasse gespeichert.

class gueterwaggonKlasse extends waggonKlasse {
  constructor(waggonTyp, waggonNummer, ladungsart, volumen) {
    super(waggonTyp, waggonNummer);
    this.ladungsart = ladungsart;
    this.volumen = volumen;
  }
} 

6) Implementierung von Klasse "Kühlwaggon". Die Klasse "Kühlwaggon" erweitert die Klasse "Güterwaggon", welche bereits die Klasse "Waggon" erweitert hat.

Die Werte der Attribute "waggonTyp", "waggonNummer", "ladungsart" und "volumen" werden an die übergeordnete Klasse mit Hilfe der Funktion "super()" weitergegeben.

class kuehlwaggonKlasse extends gueterwaggonKlasse {
  constructor(waggonTyp, waggonNummer, ladungsart, volumen) {
    super(waggonTyp, waggonNummer, ladungsart, volumen);
    this.temperatur = temperatur;
  }
  setzeTemperatur(temperatur) {
      this.temperatur = temperatur;
  }       
} 

Im folgenden wird zur Vereinfachung nur die Güterwaggon-Klasse "Kühlwaggon" berücksichtigt. Insgesamt sieht die implementierte Klassenstruktur für den Zug wie folgt aus:

class zugKlasse {
  constructor(triebWagen, waggonArray) {
    this.triebWagen = triebWagen;
    this.waggonArray = waggonArray;
    this.korrekt = false;
  }
  zugKorrekt() {
    if((this.triebWagen != undefined) && (this.triebWagen != null) && (this.waggon != undefined) && (this.waggon != null)) {
      this.korrekt = true;
    } else {
      this.korrekt = false;  
    }
  } 
} 

class triebwagenKlasse {
  constructor(lokomotivFuehrer, triebwagenTyp, triebwagenNummer) {
    this.lokomotivFuehrer = lokomotivFuehrer;
    this.triebwagenTyp = triebwagenTyp;
    this.triebwagenNummer = triebwagenNummer;
    this.nummer = 0;
  }
  setzeNummer(Nr) {
    this.nummer = Nr;
  }
} 

class lokomotivfuehrerKlasse {
  constructor(name, personalnummer) {
    this.name = name;
    this.personalnummer = personalnummer;
  }
} 

class waggonKlasse {
  constructor(waggonTyp, waggonNummer) {
    this.waggonTyp = waggonTyp;
    this.waggonNummer = waggonNummer;
  }
  setzeNummer(Nr) {
    this.nummer = Nr;
  }       
} 

class gueterwaggonKlasse extends waggonKlasse {
  constructor(waggonTyp, waggonNummer, ladungsart, volumen) {
    super(waggonTyp, waggonNummer);
    this.ladungsart = ladungsart;
    this.volumen = volumen;
  }
}

class kuehlwaggonKlasse extends gueterwaggonKlasse {
  constructor(waggonTyp, waggonNummer, ladungsart, volumen) {
    super(waggonTyp, waggonNummer, ladungsart, volumen);
    this.temperatur = 0;
  }
  setzeTemperatur(temperatur) {
    this.temperatur = temperatur;
  }       
} 

In einem Beispielprogramm sollen zwei Züge angelegt werden. Dafür werden zwei globale Variablen festgelegt:

let zug1; 
let zug2; 

Aus den Objektschablonen (Klassen) werden mit Hilfe des "new"-Schlüsselworts Objekte angelegt. Beim Anlegen eines Objekts übergibt man Werte, die in den Attributen des Objekts gespeichert werden. Die Waggons werden als Objekte angelegt und in einem Waggon-Array gespeichert. Das Waggon-Array wird beim Anlegen eines Zugobjekts im Zugobjekt gepeichert. Auf diese Weise kann man eine unterschiedliche Anzahl von Waggons in einem Zug speichern.

function setup() {
  createCanvas(400, 400);

  //zug1
  let zugfuehrer1 = new lokomotivfuehrerKlasse("Hansi Muster", "LF01");
  let triebwagen1 = new triebwagenKlasse(zugfuehrer1, "Diesel", "TW01");  
  let kuehlwaggon11 = new kuehlwaggonKlasse("Kühlwaggon", "KW01", "Fisch", 250);
  let waggonArray1 = [];
  waggonArray1.push(kuehlwaggon11);
  let kuehlwaggon12 = new kuehlwaggonKlasse("Kühlwaggon", "KW02", "Fleisch", 150);
  waggonArray1.push(kuehlwaggon12);
  zug1 = new zugKlasse(triebwagen1, waggonArray1);

  //zug2
  let zugfuehrer2 = new lokomotivfuehrerKlasse("Doris Musterline", "LF02");
  let triebwagen2 = new triebwagenKlasse(zugfuehrer2, "Elektro", "TW02");  
  let kuehlwaggon21 = new kuehlwaggonKlasse("Kühlwaggon", "KW31", "Impfstoff", 500);
  let waggonArray2 = [];
  waggonArray2.push(kuehlwaggon21);
  let kuehlwaggon22 = new kuehlwaggonKlasse("Kühlwaggon", "KW17", "Speiseeis", 460);
  waggonArray2.push(kuehlwaggon22);
  zug2 = new zugKlasse(triebwagen2, waggonArray2); 
}

Die Stärke der objektorientierten Programmierung besteht nun darin, dass der Zugriff auf eine Eigenschaft des Zuges völlig transparent erfolgt. Man weiß sofort, von welchem Objekt, welches Attribut abgefragt wird:

zug1.triebWagen.lokomotivFuehrer.personalnummer

Dieses Attribut ist die Personalnummer des Lokomotivführer des Triebwagens von Zug 1. Damit können die Eigenschaften der beiden Züge wie folgt ausgegeben werden:

function draw() {
  background(220);

  //Ausgabe Zug 1
  text("Zug 1", 10, 20);
  text("Triebwagentyp: " + zug1.triebWagen.triebwagenTyp, 10, 40);
  text("Triebwagennummer: " + zug1.triebWagen.triebwagenNummer, 10, 60);
  text("Name des Lokomotivführers: " + zug1.triebWagen.lokomotivFuehrer.name, 10, 80);
  text("Personalnummer des Lokomotivführers: " + zug1.triebWagen.lokomotivFuehrer.personalnummer, 10, 100);
  text("Waggon 1 - Typ: " + zug1.waggonArray[0].waggonTyp, 10, 120);
  text("Waggon 1 - Nummer: " + zug1.waggonArray[0].waggonNummer, 10, 140);
  text("Waggon 2 - Typ: " + zug1.waggonArray[1].waggonTyp, 10, 160);
  text("Waggon 2 - Nummer: " + zug1.waggonArray[1].waggonNummer, 10, 180);

  //Ausgabe Zug 2
  text("Zug 2", 10, 220);
  text("Triebwagentyp: " + zug2.triebWagen.triebwagenTyp, 10, 240);
  text("Triebwagennummer: " + zug2.triebWagen.triebwagenNummer, 10, 260);
  text("Name des Lokomotivführers: " + zug2.triebWagen.lokomotivFuehrer.name, 10, 280);
  text("Personalnummer des Lokomotivführers: " + zug2.triebWagen.lokomotivFuehrer.personalnummer, 10, 300);
  text("Waggon 1 - Typ: " + zug2.waggonArray[0].waggonTyp, 10, 320);
  text("Waggon 1 - Nummer: " + zug2.waggonArray[0].waggonNummer, 10, 340);
  text("Waggon 2 - Typ: " + zug2.waggonArray[1].waggonTyp, 10, 360);
  text("Waggon 2 - Nummer: " + zug2.waggonArray[1].waggonNummer, 10, 380);  
}

Man kann Attribute eines Objekts beim Programmablauf verändern. Beispielweise soll die Temperatur von Kühlwagen 2 im Zug 2 jede halbe Sekunde um eins erhöht werden. Dazu legen wir eine globale Variable let aktuelleTemperatur = 0; an und ändern deren Wert alle 30 Frames:

if(frameCount % 30 === 0) {
  aktuelleTemperatur++;
  zug2.waggonArray[1].setzeTemperatur(aktuelleTemperatur);
}

text("Waggon 2 - Temperatur: " + zug2.waggonArray[1].temperatur, 10, 400); 

Damit können wir mit folgendem Programm den Zug verwalten:

In einem neuen Fenster starten: Züge

Erweitern Sie das letzte Beispiel so, dass ein Personenzug mit einem Triebwagen, mehreren Reisezugwaggons und einem Speisewaggon zusammengestellt werden kann. Dazu müssen Sie zwei neue Klassen "Reisezugwaggon" und "Speisewaggon" anlegen, die von der Klasse "Waggon" abgeleitet werden.

Verändern Sie das letzte Beispiel in der Ausgabe so, dass die Eigenschaften des Personenzugs und Informationen zu allen angehängten Waggons ausgegebenen werden.

In einem neuen Fenster starten: Züge