Классы
В предыдущем упражнении для культур понадобились две таблицы: cropMethods с методами и метатаблица {__index = cropMethods}, новая для каждой культуры. Программисты на Lua обычно поручают обе работы одной таблице: она хранит методы, служит метатаблицей для каждой культуры, а её __index указывает на неё саму. Такая таблица называется классом:
local Crop = {}
Crop.__index = Crop
function Crop.new(name, days)
local crop = {name = name, days = days, age = 0}
setmetatable(crop, Crop)
return crop
end
function Crop:grow()
self.age = self.age + 1
end
function Crop:isReady()
return self.age >= self.days
end
local wheat = Crop.new("Пшеница", 2)
local carrot = Crop.new("Морковь", 3)
wheat:grow()
wheat:grow()
carrot:grow()
print(wheat.name, wheat:isReady())
print(carrot.name, carrot:isReady())Он выводит:
Пшеница true
Морковь falseСтрока за строкой:
local Crop = {}создаёт класс — обычную таблицу. Названия классов начинаются с заглавной буквы:Crop— класс, аwheatиcarrot— культуры.Crop.__index = Crop— главный трюк.Cropстанет метатаблицей каждой культуры, а её__index— это самCrop. Поэтому когда у культуры нет какого-то ключа, Lua ищет его вCrop, где лежат методы.Crop.new— конструктор. Он создаёт таблицу с собственными данными культуры, назначает ей метатаблицейCropи возвращает её.new— привычное имя конструктора в Lua. Вызывайте его через точку,Crop.new("Пшеница", 2): вы вызываете его у класса, когда ни одной культуры ещё нет, поэтомуselfздесь не нужен.function Crop:grow()определяет метод класса через двоеточие, а культуры вызывают его тоже через двоеточие:wheat:grow().
Объект, созданный классом, называется экземпляром этого класса: wheat и carrot — экземпляры Crop. Каждый хранит только свои данные, а методы лежат в Crop в единственном экземпляре. В отличие от многих других языков, в Lua нет специального ключевого слова для классов. Класс — это такой приём из таблицы, __index и конструктора, и вы найдёте его почти в любой большой программе на Lua.
setmetatable возвращает таблицу, которую получила, поэтому часто встречается более короткий конструктор: local crop = setmetatable({name = name, days = days, age = 0}, Crop). Он делает то же самое.
Классическая ошибка: забыть Crop.__index = Crop. Без этой строки всё выглядит нормально, пока вы не вызовете метод:
local Crop = {}
function Crop.new(name, days)
local crop = {name = name, days = days, age = 0}
setmetatable(crop, Crop)
return crop
end
function Crop:grow()
self.age = self.age + 1
end
local wheat = Crop.new("Пшеница", 2)
wheat:grow()Программа завершается с ошибкой:
main.lua:14: attempt to call a nil value (method 'grow')Crop — метатаблица пшеницы, но без __index Lua не знает, что отсутствующие ключи нужно искать в ней. Поэтому wheat.grow равно nil, а вызов nil — ошибка. (method 'grow') означает, что метод не найден: проверьте __index и проверьте, нет ли опечатки в имени метода.
Ошибки из прошлых упражнений возможны и здесь. Без return crop функция Crop.new вернёт nil (attempt to index a nil value (local 'wheat')), а wheat.grow() через точку оставит self пустым (attempt to index a nil value (local 'self')).
Задание
- 1
Запустите программу: она завершается с ошибкой
attempt to call a nil value (method 'lay'). Исправьте класс, чтобы программа выводила:Златка, яйца: 2 - 2
Добавьте метод
Hen:report(), который выводит строку о курице так же, как сейчас это делает последняя строка программы. Затем замените эту последнюю строку наgoldie:report(). Программа выводит то же самое. - 3
Создайте ещё двух кур через
Hen.new: Перлу, которая сносит 3 яйца, и Розу, которая сносит 4. Выведите их отчёты под отчётом Златки, а затем — сколько яиц у всех трёх кур вместе:Златка, яйца: 2 Перла, яйца: 3 Роза, яйца: 4 Всего яиц: 9