Menu
Coddy logo textTech

Den Konstruktor erweitern

Teil des Abschnitts Objektorientierte Programmierung der Lua-Journey von Coddy. Lektion 33 von 70.

Wenn eine untergeordnete Klasse von einer übergeordneten Klasse erbt, möchtest du häufig, dass der Konstruktor der untergeordneten Klasse alles initialisiert, was auch die übergeordnete Klasse initialisieren würde, sowie alle zusätzlichen Felder, die speziell für die untergeordnete Klasse gelten. Anstatt Code zu duplizieren, kannst du den Konstruktor der übergeordneten Klasse direkt aufrufen.

Die Technik besteht darin, den Namen der übergeordneten Klasse zu verwenden, um ihre :new()-Methode aufzurufen und dabei self als erstes Argument zu übergeben. Dadurch wird das Basisobjekt mit der gesamten Initialisierung der übergeordneten Klasse erstellt, das du anschließend für die untergeordnete Klasse anpasst.

local Vehicle = {}
Vehicle.__index = Vehicle

function Vehicle:new(speed)
    local obj = {speed = speed}
    setmetatable(obj, Vehicle)
    return obj
end

-- Car erbt von Vehicle
local Car = {}
Car.__index = Car
setmetatable(Car, {__index = Vehicle})

function Car:new(speed, brand)
    local obj = Vehicle.new(Vehicle, speed)  -- Elternkonstruktor aufrufen
    obj.brand = brand                         -- Kind-spezifisches Feld hinzufügen
    setmetatable(obj, Car)                    -- Erneut mit Car verknüpfen
    return obj
end

Beachte die wichtigsten Schritte im Konstruktor der Kindklasse: Zuerst rufen wir Vehicle.new(Vehicle, speed) auf, um ein Objekt zu erhalten, bei dem das Feld speed bereits gesetzt ist. Dann fügen wir das kinderspezifische Feld brand hinzu. Schließlich ändern wir die Metatabelle von Vehicle zu Car, damit die Instanz zuerst in der Kindklasse nach Methoden sucht.

Du kannst beim Aufrufen des Konstruktors der übergeordneten Klasse auch die Doppelpunktsyntax verwenden: Vehicle:new(speed) ist gleichbedeutend mit Vehicle.new(Vehicle, speed), da beide Vehicle als erstes Argument übergeben. Beide Formen funktionieren hier korrekt. Verwende also einfach diejenige, die du lesbarer findest.

local myCar = Car:new(120, "Toyota")
print(myCar.speed)  -- Ausgabe: 120
print(myCar.brand)  -- Ausgabe: Toyota

Dieses Muster hält deinen Code DRY: Die gemeinsame Initialisierungslogik befindet sich im übergeordneten Element, während jedes untergeordnete Element nur seine eindeutigen Attribute behandelt.

challenge icon

Aufgabe

Einfach

Erstellen wir eine Hierarchie von Lebewesen, in der eine Kindklasse den Konstruktor ihrer Elternklasse erweitert! Du erstellst eine Animal-Elternklasse und eine Bird-Kindklasse, deren Konstruktor den Konstruktor von Animal aufruft, um die gemeinsame Initialisierung zu übernehmen, bevor er ein eigenes, einzigartiges Attribut hinzufügt.

Du organisierst deinen Code auf drei Dateien verteilt:

  • Animal.lua: Definiere deine Elternklasse mit einem :new(name)-Konstruktor, der den Namen des Tiers speichert. Füge eine :describe()-Methode hinzu, die den Namen des Tiers ausgibt. Richte das standardmäßige Klassenmuster mit __index ein und gib die Klasse zurück.
  • Bird.lua: Definiere deine Kindklasse, die von Animal erbt. Der :new(name, wingspan)-Konstruktor von Bird sollte den Konstruktor der Elternklasse mit Animal.new(Animal, name) aufrufen, um den Namen zu initialisieren, anschließend das Feld wingspan zum Objekt hinzufügen und schließlich die Metatabelle wieder mit Bird verknüpfen. Füge eine :info()-Methode hinzu, die sowohl den Namen als auch die Flügelspannweite in einem bestimmten Format ausgibt.
  • main.lua: Binde dein Bird-Modul ein, lies zwei Eingaben (Name und Flügelspannweite) ein, erstelle eine Bird-Instanz und rufe beide Methoden auf, um zu zeigen, dass geerbte und kinderspezifische Funktionen zusammenarbeiten.

Du erhältst zwei Eingaben:

  1. Der Name des Vogels
  2. Die Flügelspannweite des Vogels (eine Zahl)

Erstelle in deiner Hauptdatei einen Bird mit dem angegebenen Namen und der angegebenen Flügelspannweite. Rufe anschließend :describe() und danach :info() in separaten Zeilen auf.

Die Methode :describe() (von Animal geerbt) sollte Folgendes ausgeben:

Animal: {name}

Die Methode :info() (in Bird definiert) sollte Folgendes ausgeben:

{name} has a wingspan of {wingspan}

Wenn die Eingaben beispielsweise Eagle und 200 lauten, sollte die Ausgabe folgendermaßen aussehen:

Animal: Eagle
Eagle has a wingspan of 200

Der entscheidende Punkt ist, dass dein Bird-Konstruktor die Initialisierung des Namens nicht dupliziert. Er überträgt diese Aufgabe an den Konstruktor von Animal und fügt anschließend nur das hinzu, was für Vögel einzigartig ist. So bleibt dein Code DRY und bildet gleichzeitig eine ordnungsgemäße Vererbungskette!

Probier es selbst

-- main.lua: Einstiegspunkt

local Bird = require('Bird')

-- Eingaben lesen
local name = io.read()
local wingspan = tonumber(io.read())

-- TODO: Erstelle eine Bird-Instanz mit dem gegebenen name und wingspan

-- TODO: Rufe :describe() auf dem bird auf (geerbt von Animal)

-- TODO: Rufe :info() auf dem bird auf (definiert in Bird)
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Lua-Compiler