Расширение конструктора
Часть раздела Object Oriented Programming путешествия по Lua на Coddy — урок 33 из 70.
Когда дочерний класс наследуется от родительского, вы часто хотите, чтобы конструктор дочернего класса инициализировал всё то же самое, что и родительский, плюс любые дополнительные поля, специфичные для дочернего класса. Вместо дублирования кода вы можете вызвать конструктор родителя напрямую.
Этот метод предполагает использование имени родительского класса для вызова его метода :new(), передавая родительский класс в качестве первого аргумента. Вы можете сделать это с помощью точечного синтаксиса: Vehicle.new(Vehicle, speed) или, что эквивалентно, с помощью синтаксиса с двоеточием: Vehicle:new(speed) — оба варианта допустимы и дают одинаковый результат. Это создает базовый объект со всей родительской инициализацией, который вы затем настраиваете для дочернего класса.
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Обратите внимание на основные шаги в конструкторе дочернего класса: сначала мы вызываем Vehicle.new(Vehicle, speed) (или эквивалентно Vehicle:new(speed)), чтобы получить объект с уже установленным полем speed. Затем мы добавляем специфичное для дочернего класса поле brand. Наконец, мы меняем метатаблицу с Vehicle на Car, чтобы экземпляр сначала искал методы в дочернем классе.
local myCar = Car:new(120, "Toyota")
print(myCar.speed) -- Вывод: 120
print(myCar.brand) -- Вывод: ToyotaЭтот паттерн позволяет соблюдать принцип DRY: общая логика инициализации находится в родителе, в то время как каждый дочерний элемент обрабатывает только свои уникальные атрибуты.
Задание
ЛегкоДавайте создадим иерархию существ, где дочерний класс расширяет конструктор своего родителя! Вы создадите родительский класс Animal и дочерний класс Bird, где конструктор Bird вызывает конструктор Animal для обработки общей инициализации перед добавлением своего собственного уникального атрибута.
Вы организуете свой код в трех файлах:
Animal.lua: Определите родительский класс с конструктором:new(name), который сохраняет имя животного. Включите метод:describe(), который выводит имя животного. Настройте стандартный паттерн класса с__indexи верните класс.Bird.lua: Определите дочерний класс, который наследуется от Animal. Конструктор вашего Bird:new(name, wingspan)должен вызывать родительский конструктор, используяAnimal.new(Animal, name)для инициализации имени, затем добавлять полеwingspanк объекту и, наконец, повторно привязывать метатаблицу к Bird. Включите метод:info(), который выводит и имя, и размах крыльев в определенном формате.main.lua: Подключите модуль Bird, прочитайте два входных значения (имя и размах крыльев), создайте экземпляр Bird и вызовите оба метода, чтобы продемонстрировать совместную работу унаследованной и специфичной для дочернего класса функциональности.
Вы получите два входных значения:
- Имя птицы
- Размах крыльев птицы (число)
В вашем основном файле создайте объект Bird с заданным именем и размахом крыльев. Затем вызовите :describe(), а за ним :info() на отдельных строках.
Метод :describe() (унаследованный от Animal) должен выводить:
Animal: {name}Метод :info() (определенный в Bird) должен выводить:
{name} has a wingspan of {wingspan}Например, если входные данные — Eagle и 200, вывод должен быть следующим:
Animal: Eagle
Eagle has a wingspan of 200Ключевым моментом здесь является то, что конструктор Bird не дублирует инициализацию имени — он делегирует эту работу конструктору Animal, а затем добавляет только то, что уникально для птиц. Это позволяет соблюдать принцип DRY в вашем коде при построении правильной цепочки наследования!
Шпаргалка
Чтобы вызвать конструктор родительского класса из дочернего класса, используйте имя родительского класса с точечным синтаксисом, передавая сам родительский класс в качестве первого аргумента:
local obj = ParentClass.new(ParentClass, parentArgs)После вызова родительского конструктора добавьте поля, специфичные для дочернего класса, и перепривяжите метатаблицу к дочернему классу:
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) -- Вызов родительского конструктора
obj.brand = brand -- Добавление поля, специфичного для дочернего класса
setmetatable(obj, Car) -- Перепривязка к Car
return obj
end
local myCar = Car:new(120, "Toyota")
print(myCar.speed) -- Вывод: 120
print(myCar.brand) -- Вывод: ToyotaЭтот паттерн позволяет соблюдать принцип DRY в логике инициализации — общая настройка находится в родительском классе, в то время как дочерние классы обрабатывают только свои уникальные атрибуты.
Попробуйте сами
-- main.lua: Точка входа
local Bird = require('Bird')
-- Чтение входных данных
local name = io.read()
local wingspan = tonumber(io.read())
-- TODO: Создать экземпляр Bird с заданным именем и размахом крыльев
-- TODO: Вызвать :describe() для птицы (унаследовано от Animal)
-- TODO: Вызвать :info() для птицы (определено в Bird)
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Object Oriented Programming
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигур