如何在Lua中创建类,子类和属性?

时间:2009-07-07 14:57:30

标签: class lua

我很难在Lua学习课程。毫无结果的谷歌搜索让我了解了关于元表的想法,并暗示第三方库是模拟/编写类所必需的。

这是一个示例(仅仅因为我注意到在提供示例代码时我得到了更好的答案):

public class ElectronicDevice
{
    protected bool _isOn;
    public bool IsOn { get { return _isOn; } set { _isOn = value; } }
    public  void Reboot(){_isOn = false; ResetHardware();_isOn = true; }
}

public class Router : ElectronicDevice
{
}

public class Modem :ElectronicDevice
{
    public void WarDialNeighborhood(string areaCode)
    {
        ElectronicDevice cisco = new Router();
        cisco.Reboot();
        Reboot();
        if (_isOn)
            StartDialing(areaCode);
    }
}

这是我第一次尝试使用Javier建议的技术翻译上述内容。

我接受了RBerteig的建议。但是,对派生类的调用仍会产生:"attempt to call method 'methodName' (a nil value)"

--Everything is a table
ElectronicDevice = {};

--Magic happens
mt = {__index=ElectronicDevice};

--This must be a constructor
function ElectronicDeviceFactory ()
    -- Seems that the metatable holds the fields
    return setmetatable ({isOn=true}, mt)
end

-- Simulate properties with get/set functions
function ElectronicDevice:getIsOn()  return self.isOn end
function ElectronicDevice:setIsOn(value)  self.isOn = value end
function ElectronicDevice:Reboot() self.isOn = false;
    self:ResetHardware(); self.isOn = true; end
function ElectronicDevice:ResetHardware()  print('resetting hardware...') end

Router = {};
mt_for_router = {__index=Router}

--Router inherits from ElectronicDevice
Router = setmetatable({},{__index=ElectronicDevice});

--Constructor for subclass, not sure if metatable is supposed to be different
function RouterFactory ()
    return setmetatable ({},mt_for_router)
end

Modem ={};
mt_for_modem = {__index=Modem}

--Modem inherits from ElectronicDevice
Modem = setmetatable({},{__index=ElectronicDevice});

--Constructor for subclass, not sure if metatable is supposed to be different
function ModemFactory ()
    return setmetatable ({},mt_for_modem)
end

function Modem:WarDialNeighborhood(areaCode)
        cisco = RouterFactory();
        --polymorphism
        cisco.Reboot(); --Call reboot on a router
        self.Reboot(); --Call reboot on a modem
        if (self.isOn) then self:StartDialing(areaCode) end;
end

function Modem:StartDialing(areaCode)
    print('now dialing all numbers in ' .. areaCode);
end

testDevice = ElectronicDeviceFactory();
print("The device is on? " .. (testDevice:getIsOn() and "yes" or "no") );
testDevice:Reboot(); --Ok

testRouter = RouterFactory();
testRouter:ResetHardware(); -- nil value

testModem = ModemFactory();
testModem:StartDialing('123'); -- nil value

7 个答案:

答案 0 :(得分:8)

这是代码的示例文字转录,带有一个有用的Class库,可以移动到另一个文件。

这绝不是Class的规范实施;随意定义您喜欢的对象模型。

Class = {}

function Class:new(super)
    local class, metatable, properties = {}, {}, {}
    class.metatable = metatable
    class.properties = properties

    function metatable:__index(key)
        local prop = properties[key]
        if prop then
            return prop.get(self)
        elseif class[key] ~= nil then
            return class[key]
        elseif super then
            return super.metatable.__index(self, key)
        else
            return nil
        end
    end

    function metatable:__newindex(key, value)
        local prop = properties[key]
        if prop then
            return prop.set(self, value)
        elseif super then
            return super.metatable.__newindex(self, key, value)
        else
            rawset(self, key, value)
        end
    end

    function class:new(...)
        local obj = setmetatable({}, self.metatable)
        if obj.__new then
            obj:__new(...)
        end
        return obj
    end

    return class
end

ElectronicDevice = Class:new()

function ElectronicDevice:__new()
    self.isOn = false
end

ElectronicDevice.properties.isOn = {}
function ElectronicDevice.properties.isOn:get()
    return self._isOn
end
function ElectronicDevice.properties.isOn:set(value)
    self._isOn = value
end

function ElectronicDevice:Reboot()
    self._isOn = false
    self:ResetHardware()
    self._isOn = true
end

Router = Class:new(ElectronicDevice)

Modem = Class:new(ElectronicDevice)

function Modem:WarDialNeighborhood(areaCode)
    local cisco = Router:new()
    cisco:Reboot()
    self:Reboot()
    if self._isOn then
        self:StartDialing(areaCode)
    end
end

如果您坚持使用属性的get / set方法,则不需要__index__newindex函数,并且可能只有__index表。在这种情况下,模拟继承的最简单方法是这样的:

BaseClass = {}
BaseClass.index = {}
BaseClass.metatable = {__index = BaseClass.index}

DerivedClass = {}
DerivedClass.index = setmetatable({}, {__index = BaseClass.index})
DerivedClass.metatable = {__index = DerivedClass.index}

换句话说,派生类的__index表“继承”基类的__index表。这是有效的,因为当委托给__index表时,Lua会有效地重复查找,因此调用__index表的元方法。

另外,请注意调用obj.Method(...) vs obj:Method(...)obj:Method(...)obj.Method(obj, ...)的语法糖,混合这两个调用会产生异常错误。

答案 1 :(得分:7)

你可以通过多种方式实现这一目标,但这就是我的做法(通过继承更新):

function newRGB(r, g, b)
  local rgb={
      red = r;
      green = g;
      blue = b;
      setRed = function(self, r)
          self.red = r;
      end;
      setGreen = function(self, g)
          self.green= g;
      end;
      setBlue = function(self, b)
          self.blue= b;
      end;
      show = function(self)
          print("red=",self.red," blue=",self.blue," green=",self.green);
      end;
  }
  return rgb;
end

purple = newRGB(128, 0, 128);
purple:show();
purple:setRed(180);
purple:show();

---// Does this count as inheritance?
function newNamedRGB(name, r, g, b)
    local nrgb = newRGB(r, g, b);
    nrgb.__index = nrgb; ---// who is self?
    nrgb.setName = function(self, n)
        self.name = n;
    end;
    nrgb.show = function(self)
        print(name,": red=",self.red," blue=",self.blue," green=",self.green);
    end;
    return nrgb;
end

orange = newNamedRGB("orange", 180, 180, 0);
orange:show();
orange:setGreen(128);
orange:show();

我没有实现private,protected等although it is possible

答案 2 :(得分:4)

如果您不想重新发明轮子,那么有一个很好的Lua库可以实现多个对象模型。它被称为LOOP

答案 3 :(得分:3)

我喜欢这样做的方法是实现clone()函数 请注意,这适用于Lua 5.0。我认为5.1有更多内置的面向对象的结构。

clone = function(object, ...) 
    local ret = {}

    -- clone base class
    if type(object)=="table" then 
            for k,v in pairs(object) do 
                    if type(v) == "table" then
                            v = clone(v)
                    end
                    -- don't clone functions, just inherit them
                    if type(v) ~= "function" then
                            -- mix in other objects.
                            ret[k] = v
                    end
            end
    end
    -- set metatable to object
    setmetatable(ret, { __index = object })

    -- mix in tables
    for _,class in ipairs(arg) do
            for k,v in pairs(class) do 
                    if type(v) == "table" then
                            v = clone(v)
                    end
                    -- mix in v.
                    ret[k] = v
            end
    end

    return ret
end

然后将类定义为表:

Thing = {
   a = 1,
   b = 2,
   foo = function(self, x) 
     print("total = ", self.a + self.b + x)
   end
}

要实例化它或从中派生,你可以使用clone(),你可以通过将它们作为混合物传递到另一个表(或多个表)来覆盖它们

myThing = clone(Thing, { a = 5, b = 10 })

要调用,请使用以下语法:

myThing:foo(100);

那将打印:

total = 115

要派生子类,您基本上定义了另一个原型对象:

BigThing = clone(Thing, { 
     -- and override stuff.  
     foo = function(self, x)
         print("hello");
     end
}

这个方法真的很简单,可能太简单了,但它对我的项目很有用。

答案 4 :(得分:1)

在Lua中做类似OOP的操作非常简单;只需将所有'方法'放在metatable的__index字段中:

local myClassMethods = {}
local my_mt = {__index=myClassMethods}

function myClassMethods:func1 (x, y)
    -- Do anything
    self.x = x + y
    self.y = y - x
end

............

function myClass ()
    return setmetatable ({x=0,y=0}, my_mt)

就个人而言,我从不需要继承,所以上面对我来说已经足够了。如果还不够,可以为方法表设置元表:

local mySubClassMethods = setmetatable ({}, {__index=myClassMethods})
local my_mt = {__index=mySubClassMethods}

function mySubClassMethods:func2 (....)
    -- Whatever
end

function mySubClass ()
    return setmetatable ({....}, my_mt)

<强>更新 您的更新代码中存在错误:

Router = {};
mt_for_router = {__index=Router}
--Router inherits from ElectronicDevice
Router = setmetatable({},{__index=ElectronicDevice});

请注意,您初始化Router,并从中构建mt_for_router;但随后您将Router重新分配到新表格,而mt_for_router仍然指向原始Router

Router={}替换为Router = setmetatable({},{__index=ElectronicDevice})mt_for_router初始化之前)。

答案 5 :(得分:1)

您的更新代码很冗长,但应该有效。 外,您有一个打破其中一个元词的拼写错误:

--Modem inherits from ElectronicDevice
Modem = setmetatable({},{__index,ElectronicDevice});

应该阅读

--Modem inherits from ElectronicDevice
Modem = setmetatable({},{__index=ElectronicDevice});

现有的片段使Modem metatable成为一个数组,其中第一个元素几乎肯定为零(_G.__index的通常值,除非您使用strict.lua或类似的东西)和第二个元素是ElectronicDevice

Lua Wiki描述在你更多地了解metatable之后会有意义。有一点有用的是建立一个小基础设施,使通常的模式更容易正确。

我还建议在PiL中阅读有关OOP的章节。您还需要重新阅读有关表格和元表的章节。此外,我已经链接到第1版的在线副本,但强烈建议拥有第2版的副本。 Lua Gems一书中还有一些文章与之相关。它也是推荐的。

答案 6 :(得分:1)

子类的另一种简单方法

local super    = require("your base class")
local newclass = setmetatable( {}, {__index = super } )
local newclass_mt = { __index = newclass }

function newclass.new(...) -- constructor
    local self = super.new(...)
    return setmetatable( self, newclass_mt )
end

即使覆盖了

,你仍然可以使用超类中的函数
function newclass:dostuff(...)
    super.dostuff(self,...)
   -- more code here --
end
在将self传递给超类函数时,不要忘记使用一个点