Menu
Coddy logo textTech

Расширение конструктора

Часть раздела Объектно-ориентированное программирование путешествия по Lua на Coddy. Урок 33 из 70.

Когда child наследуется от parent, часто нужно, чтобы его constructor инициализировал всё, что инициализировал бы parent, а также любые дополнительные field, специфичные для child. Вместо дублирования кода можно напрямую вызвать constructor parent.

Техника заключается в использовании имени класса parent для вызова его метода :new() с передачей self в качестве первого аргумента. Это создаёт базовый объект со всей инициализацией parent, который затем можно настроить для child.

local Vehicle = {}
Vehicle.__index = Vehicle

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

-- Car наследует от Vehicle
local Car = {}
Car.__index = Car
setmetatable(Car, {__index = Vehicle})

function Car:new(speed, brand)
    local obj = Vehicle.new(Vehicle, speed)  -- Вызов конструктора родителя
    obj.brand = brand                         -- Добавить поле, специфичное для потомка
    setmetatable(obj, Car)                    -- Перепривязать к Car
    return obj
end

Обратите внимание на ключевые шаги в конструкторе child: сначала мы вызываем Vehicle.new(Vehicle, speed), чтобы получить object с уже установленным полем speed. Затем мы добавляем специфичное для child поле brand. Наконец, мы изменяем metatable с Vehicle на Car, чтобы instance сначала искал методы в дочернем классе.

Вы также можете использовать синтаксис с двоеточием при вызове родительского конструктора: Vehicle:new(speed) эквивалентно Vehicle.new(Vehicle, speed), поскольку в обоих случаях Vehicle передаётся как первый аргумент. Здесь обе формы работают корректно, поэтому можете использовать ту, которая кажется вам более читаемой.

local myCar = Car:new(120, "Toyota")
print(myCar.speed)  -- Вывод: 120
print(myCar.brand)  -- Вывод: Toyota

Этот шаблон помогает не дублировать код: общая логика инициализации находится в Parent, а каждый Child обрабатывает только свои уникальные атрибуты.

challenge icon

Задание

Легко

Давайте построим иерархию существ, в которой дочерний class расширяет constructor родительского class! Вы создадите родительский class Animal и дочерний class Bird, где constructor Bird вызывает constructor Animal, чтобы выполнить общую инициализацию, прежде чем добавить собственный уникальный атрибут.

Вы организуете код в трёх файлах:

  • Animal.lua: Определите родительский class с constructor :new(name), который сохраняет имя животного. Включите method :describe(), выводящий имя животного. Настройте стандартный шаблон class с помощью __index и верните class.
  • Bird.lua: Определите дочерний class, который наследует Animal. constructor Bird :new(name, wingspan) должен вызвать родительский constructor с помощью Animal.new(Animal, name), чтобы инициализировать имя, затем добавить поле wingspan в object и наконец повторно связать metatable с Bird. Включите method :info(), который выводит и имя, и размах крыльев в заданном формате.
  • main.lua: Подключите модуль Bird, прочитайте два входных значения (имя и размах крыльев), создайте instance Bird и вызовите оба method, чтобы продемонстрировать совместную работу унаследованной и специфичной для дочернего class функциональности.

Вы получите два входных значения:

  1. Имя птицы
  2. Размах крыльев птицы (число)

В основном файле создайте Bird с заданными именем и размахом крыльев. Затем вызовите :describe(), а после него — :info() в отдельных строках.

Method :describe() (унаследованный от Animal) должен вывести:

Animal: {name}

Method :info() (определённый в Bird) должен вывести:

{name} has a wingspan of {wingspan}

Например, если входными значениями являются Eagle и 200, результатом должен быть:

Animal: Eagle
Eagle has a wingspan of 200

Главное здесь — то, что constructor Bird не дублирует инициализацию имени. Он делегирует эту работу constructor Animal, а затем добавляет только то, что уникально для птиц. Это помогает соблюдать принцип DRY и одновременно строить правильную цепочку наследования!

Попробуйте сами

-- main.lua: Точка входа

local Bird = require('Bird')

-- Чтение входных данных
local name = io.read()
local wingspan = tonumber(io.read())

-- TODO: Создайте экземпляр Bird с заданными name и wingspan

-- TODO: Вызовите :describe() у bird (унаследовано от Animal)

-- TODO: Вызовите :info() у bird (определено в Bird)
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор Lua