Структурирование Lua классов - PullRequest
4 голосов
/ 12 января 2020

Я создаю класс в Lua, в котором есть несколько групп связанных функций, но я не уверен, есть ли лучший способ его структурировать. В настоящее время мне приходится разрабатывать для среды Lua 5.1, но я надеюсь, что Lua 5.3 будет возможно в ближайшем будущем.

Этот класс будет использоваться в ряде различных программ Lua, поэтому Я хочу что-то, что я могу просто вставить как кусок кода (среда, для которой я программирую, означает, что модули и require не являются и не будут опцией).

В идеале мне нужен кусок кода черного ящика (за исключением открытых методов publi c), а не дублировать код в разных классах (для улучшения удобства сопровождения).

Что у меня есть в настоящее (обобщенно):

 function Fclass()

     --here I declare a bunch of local functions that can be called by any of the public methods

     local function A(parms)
     end

     --Public methods set 1

     --here I declare a bunch of state variables shared by BSelector and GetB

     local BSelector = function()
         A(parmvalues)
         --returns a bunch of iup controls with supporting (complicated) logic 
     end

     local GetB = function()
         --returns the values of the iup controls created in Bselector
     end

     --Public methods set 2

     --here I declare a bunch of state variables shared by DSelector and GetD

     local DSelector = function()
         --returns a bunch of iup controls with supporting (complicated) logic 
     end

     local GetD = function()
         A(parmvalues)
         --returns the value of the iup controls created in Dselector
     end

     return{BSelector =BSelector , GetB =GetB,  DSelector =DSelector , GetD =GetD}
 end

Группы методов "B" и "D" полностью независимы, за исключением того, что они оба используют локальные функции "A" et c. (которые не зависят от внешних переменных); их переменные состояния в идеале должны быть локальными для группы .

Это разумная структура? Или я должен разделить группы "B" и "D" на два отдельных класса и либо дублировать локальные функции, либо добавить их как отдельный фрагмент кода? Я действительно не хочу показывать локальные функции вне classe (s), потому что неизбежно будут конфликты именования ... Большинство программ будет использовать все группы методов, хотя некоторые будут использовать только одну группу.

Или есть ли лучший способ сделать это?

Я вызываю их таким образом:

myB = Fclass()
myD = Fclass()
someresults = myB.Bselector()
otherresults = myD.Dselector()

Обновлено, чтобы добавить: я рекомендовал, что я не могу использовать терминология правильно, и то, что я делаю, не классы. Мой подход основан на Программирование на Lua и был выбран, потому что я хотел сохранить переменные состояния для класса? объект? private - недоступен, кроме как через методы publi c.

Ответы [ 3 ]

1 голос
/ 14 января 2020

В вашем примере кажется, что вы инкапсулируете состояние своих экземпляров с помощью замыканий, а не значений таблиц.

Хотя это имеет преимущество более сильной инкапсуляции, так как значения повышений невидимы извне, без использования библиотеки отладки , он также имеет недостаток, заключающийся в том, что 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
0 голосов
/ 14 января 2020

Вы можете создать 2 up-значения для ваших функций класса. 1-е значение содержит переменные publi c, к которым будет обращаться вызывающий ваш класс, например, сами функции и любые параметры, обрабатываемые вызывающим.

, в то время как 2-е будет для ваших личных значений только те, которые известны и доступны изнутри класса. Вы можете использовать эту закрытую таблицу для хранения внутреннего состояния или других внутренних операций, которые не будут отображаться вызывающей стороне.

function class(first, second)

    local public = {first}
    local _private = {second}

    function _private.A(parms)
        --private function not available outside of class.
    end

    function public:selector() -- public class available to caller
        _private.A(parmvalues) -- calls private class
    end

    function public:get()
        return _private[1]
    end

    return public
end

myB = class('hello', ' world!') --passing in a variable for public, and one for private.
myD = class('hello...', ' world?')

print(myB[1] .. myB:get()) --get a public value, and uses get function to retrieve private value
print(myD[1] .. myD:get())

Кроме того, если функции класса никогда не должны изменяться вашим пользователем, вы можете принудительно применить это к меняется return public на:

    local meta = {
        __index = public,
        __newindex = function(t, k, v)
                error("this table is read-only")
              end,
        __metatable = false
    }

    return setmetatable({}, meta) -- this make the public table read only
0 голосов
/ 14 января 2020

Несколько лет go Я создал суперкласс для базовых c OOP функций в Lua.

Использование:

Person = LuaObject:extend({
    __name = "Person",
    name = "",
    age = 0,
})

-- constructor
function Person:new(name, age)
    Person.__super.new(self)-- calling the super constructor
    self.name = name
    self.age = age
end

function Person:getName()
    return self.name
end

function Person:getAge()
    return self.age
end

Не стесняйтесь использовать его :

--[[
LuaObject for basic OOP in Lua
Lua 5.0
]]
local function newIndexFunction(tbl, name, value)
    if name == "new" and type(value) == "function" then
        local constructor = value
        rawset(tbl, name, function(self, ...)
            local object = self
            if object.__class == nil then
                object = {}
                object.__class = self
                object.__id = string.sub(tostring(object), 8)

                self.__index = self
                setmetatable(object, self)
            end

            constructor(object, unpack(arg))-- Lua 5.0
            -- constructor(object, ...)-- Lua 5.1+
            return object
        end)
    else
        rawset(tbl, name, value)
    end
end

local function toStringFunction(tbl)
    return tbl:toString()
end

LuaObject = {__name = "LuaObject"}
setmetatable(LuaObject, {__newindex = newIndexFunction, __tostring = toStringFunction})

function LuaObject:extend(class)
    class = class or {}

    self.__index = self
    self.__newindex = newIndexFunction
    self.__tostring = toStringFunction

    local constructor = nil
    if class.new ~= nil then
        constructor = class.new
        class.new = nil
    end

    setmetatable(class, self)

    if constructor ~= nil then
        class.new = constructor
    end

    class.__super = self

    return class
end

function LuaObject:new()
end

function LuaObject:getSuperClass()
    return self.__super
end

function LuaObject:getClass()
    return self.__class
end

function LuaObject:toString()
    return string.format("[%s] %s", self.__class.__name, self.__id)
end

function LuaObject:isInstance(value)
    return value ~= nil and type(value) == "table" and getmetatable(value) == self
end

--[[
-- examples
-- basic class
Person = LuaObject:extend({
    __name = "Person",
    name = "",
    age = 0,
})

-- constructor
function Person:new(name, age)
    Person.__super.new(self)-- calling the super constructor
    self.name = name
    self.age = age
end

function Person:getName()
    return self.name
end

function Person:getAge()
    return self.age
end

-- extending classes
Customer = Person:extend({
    __name = "Customer",
    id = 0,
})

function Customer:new(id, name, age)
    Customer.__super.new(self, name, age)
    self.id = id
end

function Customer:getID()
    return self.id
end

-- overriding methods
function Customer:getName()
    -- calling super methods
    local realResult = Customer.__super.getName(self)
    if string.len(realResult) <= 5 then
        return realResult
    else
        return string.sub(realResult, 1, 5)
    end
end

-- testing
local customer1 = Customer:new(1, "rollback", 19)
local customer2 = Customer:new(2, "Kori", -1)

print(assert(customer1:getName() == "rollb", "Overriding of getName failed"))
print(assert(customer2:getName() == "Kori", "Overriding of getName failed"))
print(assert(customer1:getID() == 1, "Error in getID"))
print(assert(customer1:getAge() == 19, "Error in getAge"))
print(customer1)
]]
...