2009-07-07 150 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 

不要忘記用一個點時,通過自我超功能