面向对象教程

lua-users home
wiki

Lua 并不是真正面向对象的语言,它没有内置的类概念。但使用表和元表很容易创建自己的类系统。

简单的基于元表的类

local MyClass = {} -- the table representing the class, which will double as the metatable for the instances
MyClass.__index = MyClass -- failed table lookups on the instances should fallback to the class table, to get methods

-- syntax equivalent to "MyClass.new = function..."
function MyClass.new(init)
  local self = setmetatable({}, MyClass)
  self.value = init
  return self
end

function MyClass.set_value(self, newval)
  self.value = newval
end

function MyClass.get_value(self)
  return self.value
end

local i = MyClass.new(5)
-- tbl:name(arg) is a shortcut for tbl.name(tbl, arg), except tbl is evaluated only once
print(i:get_value()) --> 5
i:set_value(6)
print(i:get_value()) --> 6

首先,我们创建一个表来表示类并包含其方法。我们也让它充当实例的元表,但如果你愿意,可以使用单独的实例元表。

在构造函数中,我们创建实例(一个空表),赋予它元表,填充字段,并返回新实例。

在方法中,我们使用“self”参数来获取要操作的实例。这很常见,因此 Lua 提供了: 语法糖,它从表中调用函数条目并将表本身插入第一个参数之前。

可以做一些改进

local MyClass = {}
MyClass.__index = MyClass

setmetatable(MyClass, {
  __call = function (cls, ...)
    return cls.new(...)
  end,
})

function MyClass.new(init)
  local self = setmetatable({}, MyClass)
  self.value = init
  return self
end

-- the : syntax here causes a "self" arg to be implicitly added before any other args
function MyClass:set_value(newval)
  self.value = newval
end

function MyClass:get_value()
  return self.value
end

local instance = MyClass(5)
-- do stuff with instance...

在这里,我们在类表中添加了一个元表,该元表具有__call 元方法,当像函数一样调用值时会触发该方法。我们让它调用类的构造函数,因此在创建实例时不需要.new。另一个选择是将构造函数直接放在元方法中。在元方法中,“cls”指的是当前表。

此外,为了补充: 方法调用快捷方式,Lua 允许你在表中定义函数时使用:,这会隐式添加一个self 参数,因此你无需自己键入它。

继承

很容易扩展上面示例中类的设计以使用继承

local BaseClass = {}
BaseClass.__index = BaseClass

setmetatable(BaseClass, {
  __call = function (cls, ...)
    local self = setmetatable({}, cls)
    self:_init(...)
    return self
  end,
})

function BaseClass:_init(init)
  self.value = init
end

function BaseClass:set_value(newval)
  self.value = newval
end

function BaseClass:get_value()
  return self.value
end

---

local DerivedClass = {}
DerivedClass.__index = DerivedClass

setmetatable(DerivedClass, {
  __index = BaseClass, -- this is what makes the inheritance work
  __call = function (cls, ...)
    local self = setmetatable({}, cls)
    self:_init(...)
    return self
  end,
})

function DerivedClass:_init(init1, init2)
  BaseClass._init(self, init1) -- call the base class constructor
  self.value2 = init2
end

function DerivedClass:get_value()
  return self.value + self.value2
end

local i = DerivedClass(1, 2)
print(i:get_value()) --> 3
i:set_value(3)
print(i:get_value()) --> 5

这里我们有派生类表和一个__index 元方法,它使它继承基类。我们还将实例的创建移到了__call 元方法中,并将构造函数纯粹变成了初始化方法。这样派生类就可以在自身上调用基类初始化函数。

可以做的最后一项优化是将基类的内容复制到派生类中,而不是使用__index。这避免了可能减慢方法调用的长__index 链,并且还使得如果基类具有像__add 这样的方法,它们将在派生类上像适当的元方法一样工作。这是因为在查找元方法时不会遵循__index

local DerivedClass = {}
for k, v in pairs(BaseClass) do
  DerivedClass[k] = v
end
DerivedClass.__index = DerivedClass

类创建函数

了解了所有这些,就可以创建一个方便的函数来创建类,可以选择继承其他类。以下是一个此类函数的示例

function (...)
  -- "cls" is the new class
  local cls, bases = {}, {...}
  -- copy base class contents into the new class
  for i, base in ipairs(bases) do
    for k, v in pairs(base) do
      cls[k] = v
    end
  end
  -- set the class's __index, and start filling an "is_a" table that contains this class and all of its bases
  -- so you can do an "instance of" check using my_instance.is_a[MyClass]
  cls.__index, cls.is_a = cls, {[cls] = true}
  for i, base in ipairs(bases) do
    for c in pairs(base.is_a) do
      cls.is_a[c] = true
    end
    cls.is_a[base] = true
  end
  -- the class's __call metamethod
  setmetatable(cls, {__call = function (c, ...)
    local instance = setmetatable({}, c)
    -- run the init method if it's there
    local init = instance._init
    if init then init(instance, ...) end
    return instance
  end})
  -- return the new class table, that's ready to fill with methods
  return cls
end

基于闭包的对象

也可以使用闭包创建对象。实例创建速度较慢,占用更多内存,但也有一些优点(例如实例字段访问速度更快),并且它是一个有趣的例子,说明了如何使用闭包。

local function MyClass(init)
  -- the new instance
  local self = {
    -- public fields go in the instance table
    public_field = 0
  }

  -- private fields are implemented using locals
  -- they are faster than table access, and are truly private, so the code that uses your class can't get them
  local private_field = init

  function self.foo()
    return self.public_field + private_field
  end

  function self.bar()
    private_field = private_field + 1
  end

  -- return the instance
  return self
end

local i = MyClass(5)
print(i.foo()) --> 5
i.public_field = 3
i.bar()
print(i.foo()) --> 9

请注意,. 语法用于调用方法,而不是 :。这是因为 self 变量已经作为上值存储在方法中,因此调用代码不需要传递它。

这种方式也支持继承

local function BaseClass(init)
  local self = {}

  local private_field = init

  function self.foo()
    return private_field
  end

  function self.bar()
    private_field = private_field + 1
  end

  -- return the instance
  return self
end

local function DerivedClass(init, init2)
  local self = BaseClass(init)

  self.public_field = init2

  -- this is independent from the base class's private field that has the same name
  local private_field = init2

  -- save the base version of foo for use in the derived version
  local base_foo = self.foo
  function self.foo()
    return private_field + self.public_field + base_foo()
  end

  -- return the instance
  return self
end

local i = DerivedClass(1, 2)
print(i.foo()) --> 5
i.bar()
print(i.foo()) --> 6

基于表的类与基于闭包的类

基于表的优点

基于闭包的优点

另请参阅


最近更改 · 首选项
编辑 · 历史记录
最后编辑于 2015 年 5 月 19 日凌晨 4:47 GMT (差异)