Расширение конструктора
Часть раздела Объектно-ориентированное программирование путешествия по 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 обрабатывает только свои уникальные атрибуты.
Задание
ЛегкоДавайте построим иерархию существ, в которой дочерний 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 функциональности.
Вы получите два входных значения:
- Имя птицы
- Размах крыльев птицы (число)
В основном файле создайте 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)
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Концепция 'self'
Таблицы с функциямиЯвный 'self'Синтаксис с двоеточиемТочка против двоеточияИтоги — Движущаяся точка4Проект: Цифровой банк
Настройка проектаМетод пополнения7Полиморфизм и переопределение
Переопределение методовВызов родительских методовУтиная типизацияОбщий интерфейсПроверка типаИтоги — Роли сотрудников2Паттерн прототипа класса
Концепция прототипаСвязывание через __indexКонструктор :new()Инициализация атрибутовНезависимые экземплярыИтоги — Автозавод5Перегрузка операторов в ООП
Сложение объектовВычитание объектовКонкатенация объектовСравнение объектов (<, >)Повторение — Математика кошелька8Инкапсуляция
Соглашения об именованииЗамыкания для приватностиДоступ через замыканияТаблицы только для чтенияЛогика валидацииИтоги — Защищенное хранилище11Паттерны проектирования (Lite)
Фабричные функцииТаблица SingletonПаттерн IteratorObserver (Слушатель)Итоги — Logger Factory3Состояние и поведение объектов
Переменные экземпляраГеттерыСеттерыВычисляемые свойстваФорматирование строкПроверка на равенствоПовторение — Оценка студента6Основы наследования
Настройка наследованияНаследование методовРасширение конструктораДобавление дочерних методовОбщее и уникальноеИтоги: Иерархия фигурПотренируйтесь самостоятельно: Онлайн-компилятор Lua