Extender el constructor
Parte de la sección Object Oriented Programming del Journey de Lua de Coddy — lección 33 de 70.
Cuando una clase hija hereda de una clase padre, a menudo querrás que el constructor de la clase hija inicialice todo lo que la clase padre haría, además de cualquier campo adicional específico de la clase hija. En lugar de duplicar código, puedes llamar directamente al constructor de la clase padre.
La técnica consiste en utilizar el nombre de la clase padre para invocar su método :new(), pasando la clase padre como primer argumento. Puedes hacer esto con la sintaxis de punto: Vehicle.new(Vehicle, speed), o de forma equivalente con la sintaxis de dos puntos: Vehicle:new(speed) — ambos son válidos y producen el mismo resultado. Esto crea el objeto base con toda la inicialización del padre, que luego personalizas para el hijo.
local Vehicle = {}
Vehicle.__index = Vehicle
function Vehicle:new(speed)
local obj = {speed = speed}
setmetatable(obj, Vehicle)
return obj
end
-- Car hereda de Vehicle
local Car = {}
Car.__index = Car
setmetatable(Car, {__index = Vehicle})
function Car:new(speed, brand)
local obj = Vehicle.new(Vehicle, speed) -- Llamar al constructor padre
obj.brand = brand -- Añadir campo específico del hijo
setmetatable(obj, Car) -- Volver a vincular a Car
return obj
endObserve los pasos clave en el constructor hijo: primero, llamamos a Vehicle.new(Vehicle, speed) (o equivalentemente Vehicle:new(speed)) para obtener un objeto con el campo speed ya establecido. Luego añadimos el campo brand específico del hijo. Finalmente, cambiamos la metatabla de Vehicle a Car para que la instancia busque los métodos en la clase hija primero.
local myCar = Car:new(120, "Toyota")
print(myCar.speed) -- Salida: 120
print(myCar.brand) -- Salida: ToyotaEste patrón mantiene tu código DRY: la lógica de inicialización compartida reside en el padre, mientras que cada hijo solo maneja sus atributos únicos.
Desafío
Fácil¡Vamos a construir una jerarquía de criaturas donde una clase hija extiende el constructor de su padre! Crearás una clase padre Animal y una clase hija Bird, donde el constructor de Bird llama al constructor de Animal para manejar la inicialización compartida antes de añadir su propio atributo único.
Organizarás tu código en tres archivos:
Animal.lua: Define tu clase padre con un constructor:new(name)que almacene el nombre del animal. Incluye un método:describe()que imprima el nombre del animal. Configura el patrón de clase estándar con__indexy devuelve la clase.Bird.lua: Define tu clase hija que hereda de Animal. El constructor:new(name, wingspan)de tu Bird debe llamar al constructor padre usandoAnimal.new(Animal, name)para inicializar el nombre, luego añadir el campowingspanal objeto y, finalmente, volver a vincular la metatabla a Bird. Incluye un método:info()que imprima tanto el nombre como la envergadura en un formato específico.main.lua: Requiere tu módulo Bird, lee dos entradas (nombre y envergadura), crea una instancia de Bird y llama a ambos métodos para demostrar que la funcionalidad heredada y la específica de la hija funcionan juntas.
Recibirás dos entradas:
- El nombre del ave
- La envergadura del ave (un número)
En tu archivo principal, crea un Bird con el nombre y la envergadura dados. Luego llama a :describe() seguido de :info() en líneas separadas.
El método :describe() (heredado de Animal) debe imprimir:
Animal: {name}El método :info() (definido en Bird) debe imprimir:
{name} has a wingspan of {wingspan}Por ejemplo, si las entradas son Eagle y 200, la salida debe ser:
Animal: Eagle
Eagle has a wingspan of 200La clave aquí es que tu constructor Bird no duplica la inicialización del nombre; delega ese trabajo al constructor de Animal y luego añade solo lo que es único para las aves. ¡Esto mantiene tu código DRY mientras construyes una cadena de herencia adecuada!
Hoja de referencia
Para llamar al constructor de una clase padre desde una clase hija, utiliza el nombre de la clase padre con la sintaxis de punto, pasando la propia clase padre como primer argumento:
local obj = ParentClass.new(ParentClass, parentArgs)Después de llamar al constructor del padre, añade los campos específicos de la hija y vuelve a vincular la metatabla a la clase hija:
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) -- Llamar al constructor del padre
obj.brand = brand -- Añadir campo específico de la hija
setmetatable(obj, Car) -- Volver a vincular a Car
return obj
end
local myCar = Car:new(120, "Toyota")
print(myCar.speed) -- Salida: 120
print(myCar.brand) -- Salida: ToyotaEste patrón mantiene la lógica de inicialización DRY: la configuración compartida reside en el padre, mientras que los hijos solo manejan sus atributos únicos.
Pruébalo tú mismo
-- main.lua: Punto de entrada
local Bird = require('Bird')
-- Leer entradas
local name = io.read()
local wingspan = tonumber(io.read())
-- TODO: Crear una instancia de Bird con el nombre y la envergadura dados
-- TODO: Llamar a :describe() en el pájaro (heredado de Animal)
-- TODO: Llamar a :info() en el pájaro (definido en Bird)
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1El concepto de 'self'
Tablas con funciones'self' explícitoLa sintaxis de dos puntosPunto vs. dos puntosResumen - Punto en movimiento4Proyecto: Banco Digital
Configuración del proyectoMétodo de depósito2Patrón de prototipo de clase
El concepto de prototipoVinculación con __indexEl constructor :new()Inicialización de atributosInstancias independientesResumen - Fábrica de coches5Sobrecarga de operadores en POO
Sumar objetosRestar objetosConcatenar objetosComparar objetos (<, >)Resumen - Matemáticas de la billetera8Encapsulamiento
Convenciones de nomenclaturaClosures para privacidadAcceso mediante ClosuresTablas de solo lecturaLógica de validaciónResumen - Bóveda segura11Patrones de diseño (Lite)
Funciones FactoryTabla SingletonPatrón IteradorObserver (Listener)Resumen - Logger Factory3Estado y Comportamiento de los Objetos
Variables de InstanciaMétodos GetterMétodos SetterPropiedades CalculadasFormateo de StringsComprobaciones de IgualdadResumen - Calificación del Estudiante6Fundamentos de la herencia
Configuración de la herenciaHerencia de métodosExtender el constructorAñadir métodos hijosCompartido vs. ÚnicoResumen: Jerarquía de formas