2009-07-07 147 views
6

我很难在Lua的Grokking课程中学习。没有结果的谷歌搜索让我对meta-tables有所了解,并暗示第三方库对模拟/写入类是必要的。如何在Lua中创建类,子类和属性?

下面是一个示例(只是因为我发现我得到更好的答案时,我提供的示例代码):

​​

这是我第一次来翻译上面使用哈维尔提出的技术尝试。

我接受了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 
+0

您是否阅读过[this](http://lua-users.org/wiki/SimpleLuaClasses)? – ThibThib 2009-07-07 15:01:34

回答

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 

如果你坚持获取/属性中设置方法,你就不需要__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表。这是有效的,因为Lua在委派到__index表时,会有效地重复查询,因此将调用__index表的元方法。

另外,请谨慎拨打obj.Method(...)obj:Method(...)obj:Method(...)obj.Method(obj, ...)的语法糖,混合这两个调用会产生不寻常的错误。

6

有许多的方法可以做到这一点,但是这是我做的如何(更新与传承射门):

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(); 

我不要执行私人的,受保护的等although it is possible

1

在Lua做类似类的面向对象很容易;只是把所有的“方法”在元表的__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) 

就个人而言,我从来没有需要继承,所以上面是对我来说足够。如果这还不够,你可以设置方法表的metatable:

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初始化之前)。

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 
} 

这种方法是非常简单的,可能太简单了,但它工作得很好我的项目。

1

您的更新代码是罗嗦的,但应该工作。 除了,您有破元表中的一个错字:

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

应该读

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

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

Lua Wiki描述将有助于在对metatables更多地进行grokked之后。有帮助的一件事是构建一些基础架构,以便使正常模式更容易正确。

我也推荐阅读PiL中有关OOP的章节。您将希望重新阅读关于表和metatables的章节。此外,我已链接到第一版的在线副本,但强烈建议拥有第二版的副本。书中还有几篇关于Lua Gems的文章。它也是推荐的。

4

如果你不想重新发明轮子,有一个不错的Lua库实现几个对象模型。它被称为LOOP

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 

不要忘记用一个点时,通过自我超功能