Extender el constructor
Parte de la sección Programación Orientada a Objetos del Journey de Lua de Coddy. Lección 33 de 70.
Cuando una clase hija hereda de una clase padre, a menudo quieres que el constructor de la hija inicialice todo lo que inicializaría el padre, además de cualquier campo adicional específico de la hija. En lugar de duplicar el código, puedes llamar directamente al constructor del padre.
La técnica consiste en usar el nombre de la clase padre para invocar su método :new(), pasando self como primer argumento. Esto crea el objeto base con toda la inicialización de la clase padre, que después personalizas para la clase hija.
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) -- Llama al constructor del padre
obj.brand = brand -- Añade el campo específico del hijo
setmetatable(obj, Car) -- Re-enlaza a Car
return obj
endObserva los pasos clave en el constructor de la clase hija: primero, llamamos a Vehicle.new(Vehicle, speed) para obtener un objeto con el campo speed ya establecido. Después, añadimos el campo específico de la clase hija brand. Finalmente, cambiamos la metatabla de Vehicle a Car para que la instancia busque primero los métodos en la clase hija.
También puedes usar la sintaxis de dos puntos al llamar al constructor principal: Vehicle:new(speed) equivale a Vehicle.new(Vehicle, speed), ya que ambos pasan Vehicle como primer argumento. Cualquiera de las dos formas funciona correctamente aquí, así que puedes usar la que te resulte más legible.
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 vive en el elemento principal, mientras que cada elemento secundario solo gestiona sus atributos únicos.
Desafío
Fácil¡Construyamos una jerarquía de criaturas donde una clase child extienda el constructor de su clase parent! Crearás una clase parent Animal y una clase child Bird, cuyo constructor llama al constructor de Animal para gestionar 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 parent 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 child que hereda de Animal. El constructor:new(name, wingspan)de Bird debe llamar al constructor parent medianteAnimal.new(Animal, name)para inicializar el nombre, después añadir el campowingspanal object y, finalmente, volver a enlazar la metatable con 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 inputs (nombre y envergadura), crea una instance de Bird y llama a ambos métodos para demostrar que la funcionalidad heredada y la específica de la clase child funcionan conjuntamente.
Recibirás dos inputs:
- El nombre del ave
- La envergadura del ave (un número)
En tu archivo main, crea un Bird con el nombre y la envergadura given. Después llama a :describe() seguido de :info() en líneas separadas.
El método :describe() (inherited de Animal) debe imprimir:
Animal: {name}El método :info() (defined en Bird) debe imprimir:
{name} has a wingspan of {wingspan}Por ejemplo, si los inputs son Eagle y 200, el output debe ser:
Animal: Eagle
Eagle has a wingspan of 200La clave aquí es que tu constructor de Bird no duplica la inicialización del nombre. Delega ese trabajo al constructor de Animal, después añade solo lo que es único de las aves. ¡Esto mantiene tu código DRY mientras construyes una cadena de herencia adecuada!
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 name y wingspan dados
-- TODO: Llamar a :describe() en el bird (heredado de Animal)
-- TODO: Llamar a :info() en el bird (definido en Bird)
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
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 formasPractica por tu cuenta: Compilador de Lua online