Menu
Coddy logo textTech

Étendre le constructeur

Fait partie de la section Object Oriented Programming du Journey Lua de Coddy — leçon 33 sur 70.

Lorsqu'une classe enfant hérite d'un parent, vous voulez souvent que le constructeur de l'enfant initialise tout ce que le parent ferait, plus tous les champs supplémentaires spécifiques à l'enfant. Au lieu de dupliquer le code, vous pouvez appeler directement le constructeur du parent.

La technique consiste à utiliser le nom de la classe parente pour invoquer sa méthode :new(), en passant la classe parente comme premier argument. Vous pouvez le faire avec la syntaxe avec point : Vehicle.new(Vehicle, speed), ou de manière équivalente avec la syntaxe avec deux-points : Vehicle:new(speed) — les deux sont valides et produisent le même résultat. Cela crée l'objet de base avec toute l'initialisation parente, que vous personnalisez ensuite pour l'enfant.

local Vehicle = {}
Vehicle.__index = Vehicle

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

-- Car hérite de Vehicle
local Car = {}
Car.__index = Car
setmetatable(Car, {__index = Vehicle})

function Car:new(speed, brand)
    local obj = Vehicle.new(Vehicle, speed)  -- Appelle le constructeur parent
    obj.brand = brand                         -- Ajoute un champ spécifique à l'enfant
    setmetatable(obj, Car)                    -- Lie à nouveau à Car
    return obj
end

Remarquez les étapes clés dans le constructeur enfant : d'abord, nous appelons Vehicle.new(Vehicle, speed) (ou de manière équivalente Vehicle:new(speed)) pour obtenir un objet avec le champ speed déjà défini. Ensuite, nous ajoutons le champ brand spécifique à l'enfant. Enfin, nous changeons la métatable de Vehicle à Car pour que l'instance recherche d'abord les méthodes dans la classe enfant.

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

Ce patron de conception permet de garder votre code DRY : la logique d'initialisation partagée réside dans le parent, tandis que chaque enfant ne gère que ses attributs uniques.

challenge icon

Défi

Facile

Construisons une hiérarchie de créatures où une classe enfant étend le constructeur de son parent ! Vous allez créer une classe parente Animal et une classe enfant Bird, où le constructeur de Bird appelle le constructeur d'Animal pour gérer l'initialisation partagée avant d'ajouter son propre attribut unique.

Vous organiserez votre code sur trois fichiers :

  • Animal.lua : Définissez votre classe parente avec un constructeur :new(name) qui stocke le nom de l'animal. Incluez une méthode :describe() qui affiche le nom de l'animal. Configurez le modèle de classe standard avec __index et retournez la classe.
  • Bird.lua : Définissez votre classe enfant qui hérite d'Animal. Le constructeur :new(name, wingspan) de votre Bird doit appeler le constructeur parent en utilisant Animal.new(Animal, name) pour initialiser le nom, puis ajouter le champ wingspan à l'objet, et enfin lier à nouveau la métatable à Bird. Incluez une méthode :info() qui affiche à la fois le nom et l'envergure dans un format spécifique.
  • main.lua : Importez votre module Bird, lisez deux entrées (le nom et l'envergure), créez une instance de Bird et appelez les deux méthodes pour démontrer que les fonctionnalités héritées et spécifiques à l'enfant fonctionnent ensemble.

Vous recevrez deux entrées :

  1. Le nom de l'oiseau
  2. L'envergure de l'oiseau (un nombre)

Dans votre fichier principal, créez un Bird avec le nom et l'envergure donnés. Appelez ensuite :describe() suivi de :info() sur des lignes séparées.

La méthode :describe() (héritée d'Animal) doit afficher :

Animal: {name}

La méthode :info() (définie dans Bird) doit afficher :

{name} has a wingspan of {wingspan}

Par exemple, si les entrées sont Eagle et 200, la sortie doit être :

Animal: Eagle
Eagle has a wingspan of 200

La clé ici est que votre constructeur Bird ne duplique pas l'initialisation du nom — il délègue ce travail au constructeur d'Animal, puis ajoute seulement ce qui est unique aux oiseaux. Cela permet de garder votre code DRY tout en construisant une véritable chaîne d'héritage !

Aide-mémoire

Pour appeler le constructeur d'une classe parente à partir d'une classe enfant, utilisez le nom de la classe parente avec la syntaxe par point, en passant la classe parente elle-même comme premier argument :

local obj = ParentClass.new(ParentClass, parentArgs)

Après avoir appelé le constructeur parent, ajoutez les champs spécifiques à l'enfant et liez à nouveau la métatable à la classe enfant :

local Vehicle = {}
Vehicle.__index = Vehicle

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

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

function Car:new(speed, brand)
    local obj = Vehicle.new(Vehicle, speed)  -- Appeler le constructeur parent
    obj.brand = brand                         -- Ajouter un champ spécifique à l'enfant
    setmetatable(obj, Car)                    -- Lier à nouveau à Car
    return obj
end

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

Ce modèle permet de garder la logique d'initialisation DRY — la configuration partagée réside dans le parent, tandis que les enfants ne gèrent que leurs attributs uniques.

Essayez vous-même

-- main.lua : Point d'entrée

local Bird = require('Bird')

-- Lire les entrées
local name = io.read()
local wingspan = tonumber(io.read())

-- TODO : Créer une instance de Bird avec le nom et l'envergure donnés

-- TODO : Appeler :describe() sur l'oiseau (hérité de Animal)

-- TODO : Appeler :info() sur l'oiseau (défini dans Bird)
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Object Oriented Programming