В вашем примере кажется, что вы инкапсулируете состояние своих экземпляров с помощью замыканий, а не значений таблиц.
Хотя это имеет преимущество более сильной инкапсуляции, так как значения повышений невидимы извне, без использования библиотеки отладки , он также имеет недостаток, заключающийся в том, что Lua должен закрывать каждый метод для каждого экземпляра, тратя немного больше памяти (хотя и не так много).
Другое преимущество состоит в том, что когда переменные экземпляра реализуются как поля таблицы, их не нужно объявлять перед методом, поскольку индексация таблиц основана на строках, тогда как при реализации в виде замыканий локальное переменное должно быть известно до определения функции (это также относится к другим методам, которые в любой реализации работают одинаково). путь как переменные экземпляра).
Чаще всего переменные экземпляра хранятся в виде табличных значений внутри объекта и передают объект в качестве первого аргумента функциям. Есть даже синтактический c сахар для этого .
В Lua есть много способов делать классы с множеством различных компромиссов (некоторые лучше наследуют, а другие работайте лучше, и т. д. c.)
Поскольку вам, по-видимому, не нужно наследование, вы можете go с простой фабричной функцией, как вы уже делаете.
Мне лично нравится создавать такие фабричные функции:
local object do
local class = {}
local meta = {__index=class} -- Object metatable
function class:print() -- A method of the class
print("Hello, I am an object and my x is " .. tostring(self.x))
end
function object(self) -- The factory function for the Class
self.x = self.x or 0
return setmetatable(self, meta)
end
end
local o = object {
x = 20
}
o:print()
o.x = 30
o:print()
Это дает то преимущество, что для классов со многими методами и многими экземплярами методы не копируются в каждый экземпляр, что сохраняет немного памяти.
Кроме того, вы можете сделать что-то вроде этого
local object do
local function object_print(self)
print("Hello, I am an object and my x is " .. tostring(self.x))
end
function object(self)
self.x = self.x or 0
self.print = object_print -- Store method directly in the object
return self
end
end
Опять же, это сохраняет ссылку на каждый метод в каждом случае, тратя немного памяти. Преимущество в том, что теперь вы можете думать о классах как о чертах. Когда вы пишете
person { name = "henry" }
Вы можете думать об этом как о создании нового человека с именем Генри, но вы также можете думать об этом как о создании объекта с именем Генри и добавлении к нему характера человека.
Из-за этого преимущества объединения двух концепций OOP в одну реализацию и отсутствия неприятного наследования, это мой любимый способ построения объектов в Lua в большинстве простых случаев.
Обновление
Подход черты также позволяет определять несколько классов / черт вместе:
local person, human do
-- Some generic method shared by both classes
local function object_get_name(self)
return self.name
end
-- Person uses this as a method, but human uses
-- it as a function through an upvalue. Both work,
-- but have different upsides and downsides.
-- A method of person
local function person_say_hi(self)
print(self:get_name() .. " says hi!")
-- Calling get_name as a method here
end
-- A method of human
local function human_describe(self)
print(object_get_name(self) .. ' is a human!')
-- Calling get_name as an upvalue
end
function person(self)
self.name = self.name or 'A person'
self.say_hi = person_say_hi
self.get_name = object_get_name
-- Needs to be a method because person_say_hi assumes it to be one
return self
end
function human(self)
self.name = self.name or 'A human'
self.describe = human_describe
return self
end
end
-- Create a new person
local henry = person{ name = "Henry" }
henry:say_hi()
-- Create a new human
local steve = human { name = "Steve" }
steve:describe()
-- Change the way henry gets his name
function henry:get_name()
return self.name:upper()
end
-- This only affects henry; all other "person" objects keep their old
henry:say_hi()
-- This only works because say_hi accesses the method
-- Add the person trait to steve
person(steve)
steve:describe() -- Steve is still a human
steve:say_hi() -- Steve is also a person now