简单 Lua 类 |
|
Lua 本身没有类系统,但它强大的元编程功能使得定义经典对象变得直截了当。事实上,有很多方法可以做到这一点;再加上不熟悉的符号,这使得 Lua 中的面向对象编程一开始看起来有点吓人。
这里描述的方法是最常见和最灵活的方法,使用元表。可以通过给一个表赋予一个元表来定制它的行为。例如,如果元表有一个 __index
函数,那么任何在表中查找东西的失败尝试都会传递给 __index
。如果 __index
本身是一个表,则将在该表中查找符号。(请参阅 PiL 中的精彩讨论 [1])以下是基本思路
Account = {} Account.__index = Account function Account:create(balance) local acnt = {} -- our new object setmetatable(acnt,Account) -- make Account handle lookup acnt.balance = balance -- initialize our object return acnt end function Account:withdraw(amount) self.balance = self.balance - amount end -- create and use an Account acc = Account:create(1000) acc:withdraw(100)
这里,Account 对象由表表示,这些表只包含一个字段,即余额。Lua 尝试在 acc 中查找 withdraw
,但找不到。因为 acc 有一个定义了 __index
的元表,它将随后在该元表中查找 withdraw
。所以 acc:withdraw(100)
实际上是调用 Account.withdraw(acc,100)
。我们实际上可以将 withdraw
直接放入 acc
中,但这将是浪费和不灵活的 - 添加一个新方法将需要更改 create
函数等。
我将定义一个函数 class
,它将透明地完成所有这些(以及更多)。
Account = class(function(acc,balance) acc.balance = balance end) function Account:withdraw(amount) self.balance = self.balance - amount end -- can create an Account using call notation! acc = Account(1000) acc:withdraw(100)
在这个方案中,你为新类提供一个初始化函数,并自动生成一个“构造函数”。
支持简单的继承。例如,这里定义了一个基类 Animal
,并声明了几种特定类型的动物。所有使用 class
创建的类都有一个 is_a
方法,你可以使用它在运行时找出实际的类
-- animal.lua require 'class' Animal = class(function(a,name) a.name = name end) function Animal:__tostring() return self.name..': '..self:speak() end Dog = class(Animal) function Dog:speak() return 'bark' end Cat = class(Animal, function(c,name,breed) Animal.init(c,name) -- must init base! c.breed = breed end) function Cat:speak() return 'meow' end Lion = class(Cat) function Lion:speak() return 'roar' end fido = Dog('Fido') felix = Cat('Felix','Tabby') leo = Lion('Leo','African')
D:\Downloads\func>lua -i animal.lua > = fido,felix,leo Fido: bark Felix: meow Leo: roar > = leo:is_a(Animal) true > = leo:is_a(Dog) false > = leo:is_a(Cat) true
Animal
类只定义了 __tostring
方法,Lua 会在需要对象字符串表示时使用它。而 __tostring
方法依赖于 speak
方法,但 speak
方法并没有定义。所以,Animal
类相当于 C++ 中的抽象基类;像 Dog
这样的具体派生类则定义了 speak
方法。请注意,如果派生类有自己的初始化函数,它们必须显式地调用基类的 init
函数。
class()
函数的实现
class()
函数使用两个技巧。它允许你使用调用符号(如上面的 Dog('fido')
)来构造一个类,方法是给类本身一个元表,该元表定义了 __call
方法。它通过复制基类的字段到派生类来处理继承。这不是实现继承的唯一方法;我们可以将 __index
设置为一个函数,该函数显式地尝试在基类中查找函数。但这种方法会带来更好的性能,代价是使类对象稍微臃肿一些。每个派生类都保留一个 _base
字段,该字段包含基类,但这是为了实现 is_a
方法。
注意,在运行时修改基类不会影响其子类。
-- class.lua -- Compatible with Lua 5.1 (not 5.0). function class(base, init) local c = {} -- a new class instance if not init and type(base) == 'function' then init = base base = nil elseif type(base) == 'table' then -- our new class is a shallow copy of the base class! for i,v in pairs(base) do c[i] = v end c._base = base end -- the class will be the metatable for all its objects, -- and they will look up their methods in it. c.__index = c -- expose a constructor which can be called by <classname>(<args>) local mt = {} mt.__call = function(class_tbl, ...) local obj = {} setmetatable(obj,c) if init then init(obj,...) else -- make sure that any stuff from the base class is initialized! if base and base.init then base.init(obj, ...) end end return obj end c.init = init c.is_a = function(self, klass) local m = getmetatable(self) while m do if m == klass then return true end m = m._base end return false end setmetatable(c, mt) return c end
--- class_orig.lua 2009-07-24 20:53:25.218750000 -0400 +++ class.lua 2009-07-24 20:53:49.734375000 -0400 @@ -21,8 +21,8 @@ mt.__call = function(class_tbl,...) local obj = {} setmetatable(obj,c) - if ctor then - ctor(obj,...) + if class_tbl.init then + class_tbl.init(obj,...) else -- make sure that any stuff from the base class is initialized! if base and base.init then
A = class() function A:init(x) self.x = x end function A:test() print(self.x) end B = class(A) function B:init(x,y) A.init(self,x) self.y = y end
function A:__add(b) return A(self.x + b.x) end
c.init = ctor. 我将此参数的名称更改为 'init'。--DeniSpir
function class(def) local class = {} local parents = {} local upv local env = _G local wraps local function super(parent_class) if not parent_class then parent_class = parents[1] end local this = this local that = {} for k,v in pairs(parent_class) do that[k] = type(v) == 'function' and wraps(this, v) or v end return setmetatable(that, that) end function wraps(this, func) return function(...) local t = env.this local s = env.super env.this = this env.super = super local ret = pcall(func, ...) env.this = t env.super = s return ret end end function class.__init()end for i=1,math.huge do inherit, v = debug.getlocal(def, i) if not inherit then break end local parent_class = _G[inherit] for i=1,math.huge do local name, pclass = debug.getlocal(2,i,1) if not name then break elseif name == inherit then parent_class = pclass break end end if parent_class and type(parent_class) == 'table' then table.insert(parents, parent_class) for k,v in pairs(parent_class) do class[k] = v end else error(string.format('Class "%s" not valid.', name)) end end for i=1,math.huge do local name, value = debug.getupvalue(def, i) if not name then break elseif name == '_ENV' then env = value upv = i break end end local _env = setmetatable({}, { __index= function(t, name) local value = class[name] return value ~= nil and value or env[name] end, __newindex = function(t, name, value) class[name] = value end }) local function senv(env) if upv then debug.setupvalue(def, upv, env) else _G = env end end senv(_env) env.pcall(def, env.table.unpack(parents)) senv(env) return setmetatable({}, { __ipairs = function() return ipairs(class) end, __pairs = function() return pairs(class) end, __index = function(t, name) return class[name] end, __index_new = function(t, name, value) class[name] = value end, __call = function(...) local this = {} for k,v in pairs(class) do this[k] = type(v) == 'function' and wraps(this, v) or v end this.__class = class this.__init(...) return setmetatable(this, this) end }) end
global = true Inherit = class(function() this_is_a_property_of_Inherit = true function __init() print('Inherit().__init()') this.init = true end function __call() print('Yay! You\'re calling for me :) init:', this.init, '\n') end end) Example = class(function(Inherit) print('Inherited property:', this_is_a_property_of_Inherit) print('Global variable: ', global, '\n') function __init() print('Example().__init()') super().__init() print('this.init:', this.init) end function test(...) print(..., this.__init, '\n') end end) example = Example() example.test('__init:') example() example.property = 'I\'m a property of instance "example"' print('example.property', example.property) print('Example.property', Example.property) -- Inherited property: true -- Global variable: true -- Example().__init() -- Inherit().__init() -- this.init: true -- __init: function: 0x15dd5f0 -- Yay! You're calling for me :) init: true -- example.property I'm a property of instance "example" -- Example.property nil