Классы с помощью метатаблиц
Моделируйте классы с помощью таблиц и __index для поиска методов.
«Классы с помощью метатаблиц» — бесплатный урок Lua Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Lua Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Lua Academy содержит 4 уроков всего.
Шаблон класса на основе таблицы
В Lua «класс» — это всего лишь таблица, хранящая методы. Экземпляры являются таблицами, у метатаблицы которых __index указывает на таблицу класса. Благодаря этому все методы доступны экземплярам через цепочку поиска.
local Animal = {}
Animal.__index = Animal
function Animal.new(name, sound)
return setmetatable({name=name, sound=sound}, Animal)
end
function Animal:speak()
print(self.name .. " says " .. self.sound)
end
local dog = Animal.new("Rex","Woof")
dog:speak() -- Rex says Woof__index указывает на класс
Установка ClassName.__index = ClassName означает «искать отсутствующие ключи в таблице класса». При вызове метода экземпляра Lua не находит его в экземпляре, проверяет метатаблицу (__index), находит класс и ищет метод уже в нём.
local Point = {}
Point.__index = Point
Point.type = "2D Point"
function Point.new(x,y)
return setmetatable({x=x,y=y}, Point)
end
function Point:magnitude()
return math.sqrt(self.x^2 + self.y^2)
end
local p = Point.new(3,4)
print(p:magnitude()) -- 5.0
print(p.type) -- 2D Point (from class)Шаблон конструктора
Функция new() — это конструктор: она создаёт таблицу с данными экземпляра и возвращает её, обернув в метатаблицу, указывающую на класс. Это стандартный способ создания объектов в Lua.
local Circle = {}
Circle.__index = Circle
function Circle.new(cx, cy, radius)
assert(radius > 0, "radius must be positive")
return setmetatable({cx=cx, cy=cy, r=radius}, Circle)
end
function Circle:area()
return math.pi * self.r^2
end
function Circle:perimeter()
return 2 * math.pi * self.r
end
local c = Circle.new(0, 0, 5)
print(string.format("Area: %.2f", c:area()))Синтаксис метода и функции
obj:method() — это сокращённая запись для obj.method(obj). Определяйте методы с синтаксисом двоеточия (function Cls:method()), чтобы неявно получить self. Вызывайте их с двоеточием, чтобы передать экземпляр. Смешивание точечного синтаксиса и синтаксиса с двоеточием приводит к трудноуловимым ошибкам.
local Dog = {}
Dog.__index = Dog
-- Define with colon: self is implicit
function Dog:bark(times)
for i = 1, (times or 1) do
print(self.name .. ": Woof!")
end
end
function Dog.new(name)
return setmetatable({name=name}, Dog)
end
local d = Dog.new("Buddy")
d:bark(2) -- Buddy: Woof! (x2)
-- Dog.bark(d,1) -- same thing, explicit selfПоля класса и поля экземпляра
Поля в таблице класса являются общими для всех экземпляров (переменные класса). Поля в таблице экземпляра относятся только к этому экземпляру. Экземпляр может скрыть поле класса, создав собственное поле с таким же именем.
local Entity = {}
Entity.__index = Entity
Entity.count = 0 -- class-level counter
function Entity.new(name)
Entity.count = Entity.count + 1
return setmetatable({name=name, id=Entity.count}, Entity)
end
local a = Entity.new("alpha")
local b = Entity.new("beta")
local c = Entity.new("gamma")
print(Entity.count) -- 3
print(a.id, b.id, c.id) -- 1 2 3tostring для классов
Добавьте __tostring в таблицу класса, чтобы вывод при печати был понятнее. Поскольку метатаблицей экземпляра является класс, а в классе есть __tostring, все экземпляры будут использовать его автоматически.
local Vec2 = {}
Vec2.__index = Vec2
Vec2.__tostring = function(v)
return string.format("Vec2(%g, %g)", v.x, v.y)
end
function Vec2.new(x,y)
return setmetatable({x=x,y=y}, Vec2)
end
function Vec2.__add(a, b)
return Vec2.new(a.x+b.x, a.y+b.y)
end
local v1 = Vec2.new(1,2)
local v2 = Vec2.new(3,4)
print(v1+v2) -- Vec2(4, 6)Проверка типа экземпляра
Проверьте, является ли объект экземпляром класса, сравнив его метатаблицу с таблицей класса. Это эквивалент операции instanceof в Lua.
local Dog = {}
Dog.__index = Dog
function Dog.new(name)
return setmetatable({name=name}, Dog)
end
local function isDog(obj)
return getmetatable(obj) == Dog
end
local d = Dog.new("Rex")
local t = {}
print(isDog(d)) -- true
print(isDog(t)) -- falseПриватные методы
Приватные методы — это локальные функции в файле модуля. Их нельзя вызвать извне, но они доступны внутри общедоступных методов модуля как внешние переменные.
local Account = {}
Account.__index = Account
-- Private helper
local function validate(amount)
return type(amount)=="number" and amount > 0
end
function Account.new(balance)
return setmetatable({balance=balance or 0}, Account)
end
function Account:deposit(amount)
assert(validate(amount), "invalid amount")
self.balance = self.balance + amount
end
function Account:getBalance() return self.balance endСравнение классов
Переопределите __eq и __lt для пользовательского сравнения на равенство и упорядочивания. Lua вызывает __eq для таблиц только тогда, когда оба операнда используют одну и ту же метатаблицу.
local Temp = {}
Temp.__index = Temp
Temp.__eq = function(a,b) return a.celsius == b.celsius end
Temp.__lt = function(a,b) return a.celsius < b.celsius end
Temp.__tostring = function(t)
return t.celsius.."°C"
end
function Temp.new(c)
return setmetatable({celsius=c}, Temp)
end
local t1 = Temp.new(100)
local t2 = Temp.new(0)
print(t1 == Temp.new(100)) -- true
print(t2 < t1) -- trueНесколько классов
Каждый класс — независимая таблица со своей метатаблицей. Объекты разных классов имеют разные метатаблицы. Это основа объектно-ориентированной системы с несколькими классами в Lua.
local Cat = {}
Cat.__index = Cat
function Cat.new(name) return setmetatable({name=name,lives=9},Cat) end
function Cat:loseLife() self.lives=self.lives-1; return self.lives end
local Dog2 = {}
Dog2.__index = Dog2
function Dog2.new(name) return setmetatable({name=name,tricks={}},Dog2) end
function Dog2:learn(t) self.tricks[#self.tricks+1]=t end
local c = Cat.new("Whiskers")
local d = Dog2.new("Buddy")
d:learn("sit"); d:learn("stay")
print(c:loseLife(), #d.tricks) -- 8 2Быстрая проверка
Зачем устанавливать ClassName.__index = ClassName?
Итоги: классы через метатаблицы
Краткое содержание:
- Класс = таблица +
__index = self - Конструктор:
function Cls.new(...) return setmetatable({}, Cls) end - Методы: определяются в таблице класса и вызываются с синтаксисом двоеточия
- Поля класса и экземпляра: поля класса являются общими, поля экземпляра относятся к отдельному объекту
- Проверка типа:
getmetatable(obj) == Cls
Часто задаваемые вопросы
Урок «Классы с помощью метатаблиц» бесплатный?
Да — полный текст урока «Классы с помощью метатаблиц» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Lua Academy, подпишись на CoddyKit PRO. Курс Lua Academy содержит 4 уроков всего.
Чему я научусь в уроке «Классы с помощью метатаблиц»?
Моделируйте классы с помощью таблиц и __index для поиска методов. Ты практикуешь Lua Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Lua Academy?
Предыдущий опыт не требуется. Lua Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Классы с помощью метатаблиц»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Lua Academy?
Да. Каждый урок Lua Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Классы с помощью метатаблиц
- Конструкторы и new()
- Методы экземпляров и self
- Инкапсуляция с замыканиями