Kiste
EN

Klassen und Objekte

Verfügbar — implementiert in Phase B.1

Eine Klasse ist eine Schablone für Objekte. Stell dir eine Klasse vor wie einen Bauplan für ein Haus — viele Häuser können nach diesem Bauplan gebaut werden, jedes Haus ist seine eigene Instanz.

13.1 Eine einfache Klasse definieren

klasse Person {
    funktion neu(name, alter) {
        dies.name = name
        dies.alter = alter
    }

    funktion grüße() {
        sag "Hallo, ich bin " + dies.name
    }
}

Erklärung Stück für Stück:

  • klasse Person { ... } — definiert eine Klasse namens Person
  • funktion neu(...) — der Konstruktor, läuft beim Erstellen einer Instanz
  • dies — verweist auf das aktuelle Objekt (in anderen Sprachen: this/self)
  • dies.name = name — speichert ein Feld (Eigenschaft) am Objekt
  • funktion grüße() — eine Methode (Funktion, die zur Klasse gehört)

13.2 Instanzen erstellen mit neu

nimm sascha = neu Person("Sascha", 38)
nimm anna = neu Person("Anna", 30)

sascha.grüße()    // Hallo, ich bin Sascha
anna.grüße()      // Hallo, ich bin Anna

neu Klasse(...) erstellt eine neue Instanz und ruft den Konstruktor auf.

Wichtig: Jede Instanz hat ihre eigenen Felder. sascha.name und anna.name sind verschiedene Werte.

13.3 Felder lesen und schreiben

nimm p = neu Person("Sascha", 38)

sag p.name        // Sascha — Feld lesen
sag p.alter       // 38

p.alter = 39      // Feld direkt überschreiben
sag p.alter       // 39

13.4 Methoden mit Rückgabewerten

klasse Rechner {
    funktion neu(start) {
        dies.wert = start
    }

    funktion plus(n) {
        gib dies.wert + n
    }

    funktion mal(n) {
        gib dies.wert * n
    }
}

nimm r = neu Rechner(10)
sag r.plus(5)     // 15
sag r.mal(3)      // 30

13.5 Methoden, die andere Methoden aufrufen

dies.methode() ruft eine andere Methode des Objekts auf:

klasse Zähler {
    funktion neu() {
        dies.wert = 0
    }

    funktion erhöhe() {
        dies.wert = dies.wert + 1
    }

    funktion erhöhe_um(n) {
        für i = 1 bis n {
            dies.erhöhe()        // ← Methoden-Aufruf auf sich selbst
        }
    }
}

nimm z = neu Zähler()
z.erhöhe_um(5)
sag z.wert        // 5

13.6 Vererbung mit erbt

Eine Klasse kann eine andere als Eltern-Klasse haben — sie erbt dann alle Methoden.

klasse Tier {
    funktion neu(name) {
        dies.name = name
    }

    funktion vorstellen() {
        sag "Ich bin " + dies.name
    }
}

klasse Hund erbt Tier {
    funktion bellen() {
        sag dies.name + " bellt: Wuff!"
    }
}

nimm rex = neu Hund("Rex")
rex.vorstellen()    // Ich bin Rex   ← geerbt von Tier
rex.bellen()        // Rex bellt: Wuff!

Was passiert:

  • Hund definiert nur bellen() selbst
  • vorstellen() und der Konstruktor neu() kommen automatisch von Tier

13.7 Methoden überschreiben

Ein Kind kann Methoden des Eltern-Typs überschreiben — die eigene Version gewinnt:

klasse Tier {
    funktion sage() {
        sag "Tiergeräusch"
    }
}

klasse Katze erbt Tier {
    funktion sage() {
        sag "Miau"        // ← überschreibt die geerbte Version
    }
}

nimm k = neu Katze()
k.sage()    // Miau

13.8 Konstruktor in Kindklasse

Wenn die Kindklasse eigene Felder hat, definiert sie einen eigenen Konstruktor:

klasse Tier {
    funktion neu(name) {
        dies.name = name
    }
}

klasse Hund erbt Tier {
    funktion neu(name, rasse) {
        dies.name = name
        dies.rasse = rasse
    }
}

nimm rex = neu Hund("Rex", "Schäferhund")
sag rex.name      // Rex
sag rex.rasse     // Schäferhund

13.9 Klassen in Interpolation

Felder lassen sich direkt in Interpolations-Strings nutzen:

klasse Person {
    funktion neu(name, alter) {
        dies.name = name
        dies.alter = alter
    }

    funktion beschreibung() {
        gib "{dies.name} ist {dies.alter}"
    }
}

nimm p = neu Person("Anna", 25)
sag p.beschreibung()    // Anna ist 25

13.10 Was passiert bei nicht existierenden Feldern?

klasse Leer {
    funktion neu() { }
}

nimm x = neu Leer()
sag x.gibtsNicht       // nichts

Zugriff auf ein nicht existierendes Feld liefert nichts — kein Fehler. Genau wie bei Karten.

13.11 Zwei Instanzen vergleichen

nimm a = neu Person("Sascha", 38)
nimm b = neu Person("Sascha", 38)
nimm c = a

sag a == b    // falsch — verschiedene Objekte (Identitäts-Vergleich)
sag a == c    // wahr — gleiches Objekt

Wichtig: Selbst wenn zwei Instanzen die gleichen Werte haben, sind sie standardmäßig verschiedene Objekte (Identitäts-Vergleich). Du kannst das aber ändern: mit der Magic-Method gleicht wird == zum Wert-Vergleich — siehe Kapitel 32.

Weiter geht's in Kapitel 32: Dort lernst du zwei mächtige Erweiterungen für Klassen:

  • super — im Konstruktor und in Methoden die Eltern-Klasse aufrufen (super.neu(...), super.methode()), statt geerbte Felder mühsam von Hand neu zu setzen wie in §13.8.
  • Magic-Methodsals_text (eigene Anzeige bei sag/Interpolation), gleicht (==), vergleicht_mit (</>), länge.

13.12 Tipps für die Praxis

Tu:

  • Klassen-Namen mit GroßSchreibung beginnen (Person, BankKonto)
  • Kurze, eindeutige Klassen-Namen
  • Konstruktor (neu) sollte alle Pflichtfelder setzen
  • Eine Klasse sollte eine Sache gut machen

Vermeide:

  • Riesige Klassen mit 50 Feldern und 30 Methoden
  • Tiefe Vererbungs-Hierarchien (mehr als 2-3 Ebenen)
  • Felder ohne Zweck — wenn du's nicht nutzt, weg damit

13.13 Listen von Objekten

Bisher hielten unsere Listen (Kapitel 9) Zahlen oder Text. Eine Liste darf aber genauso Objekte — also Instanzen deiner Klassen — enthalten. So verwaltest du viele gleichartige Dinge auf einmal: alle Produkte, alle Spieler, alle Ebenen eines Bildes. Auf jedes Element greifst du mit dem gewohnten Punkt zu — Felder und Methoden:

klasse Produkt {
    funktion neu(name, preis) {
        dies.name = name
        dies.preis = preis
    }
    funktion teuer() { gib dies.preis > 15 }
}

nimm produkte = [neu Produkt("Apfel", 10), neu Produkt("Birne", 20)]

sag produkte[0].name      // Apfel   — Feld des ersten Elements
sag produkte[1].preis     // 20
sag produkte[1].teuer()   // wahr    — Methode des zweiten Elements

Mit wiederhole (Kapitel 7) gehst du bequem über alle Objekte:

wiederhole p in produkte {
    sag "{p.name}: {p.preis}"
}
// Apfel: 10
// Birne: 20

Das funktioniert auch aus Funktionen heraus — du kannst eine Objekt-Liste übergeben und darin per Index auf Felder und Methoden zugreifen:

funktion gesamtpreis(waren) {
    nimm summe = 0
    für i = 0 bis länge(waren) - 1 {
        summe = summe + waren[i].preis
    }
    gib summe
}

sag gesamtpreis(produkte)   // 30

Und die Listen-Helfer aus Kapitel 17 (gefiltert, sortiere, abgebildet, gesammelt …) arbeiten genauso über Objekte — du filterst und sortierst nach Feldern:

nutze liste

nimm teure = liste.gefiltert(produkte, funktion(p) { gib p.preis > 15 })
sag länge(teure)            // 1   — nur die Birne

nimm namen = liste.abgebildet(produkte, funktion(p) { gib p.name })
wiederhole n in namen { sag n }
// Apfel
// Birne

Merke: Eine Liste ist immer homogen — alle Elemente müssen dieselbe Klasse haben (nicht Produkt und Kunde gemischt). Und Listen bleiben flach: eine Liste von Objekten ja, aber keine Liste von Listen. Beides passt gut zum Klassen-Denken — brauchst du eine Struktur in der Struktur, gib dem Objekt einfach ein Feld dafür.

Objekte, die selbst Objekt-Listen halten

Genau das ist der übliche Weg zu verschachtelten Strukturen: Statt einer Liste von Listen gibt eine Klasse einer ihrer Felder eine Objekt-Liste. So hält ein Bild eine Liste von Ebene-Objekten, und du sprichst ein Element über zwei Stufen an — auch aus Funktionen heraus:

nutze liste

klasse Ebene {
    funktion neu(größe) { dies.pixel = liste.gefüllt(größe, 0) }
    funktion setze(i, wert) {
        nimm p = dies.pixel
        p[i] = wert
    }
    funktion hole(i) { gib dies.pixel[i] }
}

klasse Bild {
    funktion neu(größe) {
        dies.ebenen = [neu Ebene(größe), neu Ebene(größe)]
    }
}

nimm bilder = [neu Bild(4)]

funktion male(bi, ei, i, wert) { bilder[bi].ebenen[ei].setze(i, wert) }
funktion lies(bi, ei, i) { gib bilder[bi].ebenen[ei].hole(i) }

male(0, 1, 2, 7)
sag lies(0, 1, 2)   // 7

bilder[bi].ebenen[ei].setze(…) liest sich von links nach rechts: das Bild an Stelle bi, darin die Ebene ei, darauf die Methode. Feld lesen/schreiben (bilder[0].ebenen[0].pixel), das Element in einer eigenen Variablen festhalten (nimm eb = bilder[0].ebenen[1]) und die Reihenfolge tauschen gehen genauso.

Dynamisch wachsen darf so eine Objekt-Liste auch — mit liste.hänge_an/liste.entferne fügst du zur Laufzeit neue Objekte hinzu oder entfernst welche (die Liste bleibt homogen, also nur dieselbe Klasse):

funktion neues_bild(größe) {
    liste.hänge_an(bilder, neu Bild(größe))
}

sag länge(bilder)     // 1
neues_bild(4)
sag länge(bilder)     // 2

Tipp für Konstruktoren, die andere Objekte bauen: die Größen-Parameter (größe) bekommen ihren Typ am einfachsten aus der Verwendung — z. B. dies.pixel = liste.gefüllt(größe, 0) sagt Kiste, dass größe eine Ganzzahl ist. Text- oder Zustandsfelder, die nicht aus der Größe folgen, belegst du im Konstruktor mit einem festen Wert vor (dies.name = "") und setzt sie nach dem Erzeugen über das Feld (bild.ebenen[0].name = "Hintergrund").