Наследование
Яблоня — тоже культура: она растёт день за днём, и её можно описать как любую другую культуру. Но у неё есть кое-что ещё: она даёт плоды. Можно было бы скопировать весь класс Crop и дописать его, но тогда каждое изменение для культур пришлось бы вносить дважды. Вместо этого один класс может наследовать от другого: он получает все методы другого класса, добавляет свои и заменяет те, которые должны работать иначе:
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:describe()
return self.name .. ", день " .. self.age .. " из " .. self.days
end
local Tree = {}
Tree.__index = Tree
setmetatable(Tree, {__index = Crop})
function Tree.new(name, days, fruit)
local tree = Crop.new(name, days)
setmetatable(tree, Tree)
tree.fruit = fruit
return tree
end
function Tree:describe()
return Crop.describe(self) .. ", плоды: " .. self.fruit
end
local wheat = Crop.new("Пшеница", 2)
local apple = Tree.new("Яблоня", 30, "яблоки")
wheat:grow()
apple:grow()
print(wheat:describe())
print(apple:describe())Он выводит:
Пшеница, день 1 из 2
Яблоня, день 1 из 30, плоды: яблокиЧто здесь нового:
local Tree = {}иTree.__index = TreeделаютTreeклассом, как иCrop.setmetatable(Tree, {__index = Crop})даёт классуTreeметатаблицу: то, чего нет вTree, Lua ищет вCrop. Эта строка и есть наследование.Tree— подкласс классаCrop, аCrop— его родительский класс.Tree.newне повторяет то, что делаетCrop.new. Он вызываетCrop.new, чтобы получить культуру с названием, сроком и возрастом, затем превращает её в дерево черезsetmetatable(tree, Tree)(у таблицы только одна метатаблица, поэтому это заменяетCrop) и добавляет собственный ключ дерева —fruit.
Когда вы вызываете apple:grow(), Lua ищет grow шаг за шагом: в самой apple (нет), в Tree через метатаблицу яблони (нет) и в Crop через метатаблицу Tree (есть). Получается цепочка: apple → Tree → Crop.
Переопределение метода. У Tree есть собственный describe. Lua прекращает поиск на первом найденном describe, а для дерева это тот, что в Tree. Поэтому он заменяет (переопределяет) метод из Crop. Пшеница — обычная культура, поэтому она по-прежнему использует describe из Crop. Версии для дерева не нужно повторять работу родителя: Crop.describe(self) вызывает родительский метод для того же дерева. Обратите внимание на точку и self, который передан вручную. self:describe() снова вызвал бы собственный describe дерева, который опять вызвал бы себя, и так без конца, пока программа не завершится с ошибкой stack overflow.
Типичные ошибки:
- Забыть
setmetatable(Tree, {__index = Crop}): деревья не найдут методы культур, иapple:grow()завершится с ошибкойattempt to call a nil value (method 'grow'). - Забыть
setmetatable(tree, Tree)вTree.new: новое дерево останется обычной культурой. Тогдаapple:describe()использует версию изCropи ничего не скажет о плодах, а собственные методы дерева не найдутся. - Ждать, что метаметоды наследуются. Lua ищет
__tostring,__lt,__addи остальные прямо в метатаблице объекта, а не через__index. Если уCropесть__tostring, деревья всё равно выводятся какtable: 0x…, пока вы не добавитеTree.__tostring = Crop.__tostring.
Задание
- 1
Под
Animal:speakначните классCow, который наследует отAnimal: создайте таблицу, задайте её__indexи дайте ей метатаблицу, у которой__index— этоAnimal. - 2
Добавьте конструктор
Cow.new(name). Он создаёт животное черезAnimal.newсо звуком"му", превращает его в корову черезsetmetatable, добавляет ключmilkсо значением0и возвращает корову. Добавьте также методCow:giveMilk(liters), который прибавляетlitersкmilk. В конце программы создайте Беллу черезCow.new("Белла"), пусть она даст 8 литров молока, а затем 6, и выведите, что она говорит. Программа выводит:Рекс говорит: гав Белла говорит: му - 3
Переопределите
speakдля коров:Cow:speak()возвращает то, что возвращаетAnimal.speak, а после этого — сколько молока корова дала. Рекс — обычное животное, поэтому он по-прежнему просто говоритгав. Программа выводит:Рекс говорит: гав Белла говорит: му, надоено молока: 14 л