面向对象教程

lua-users home
wiki

Lua 并不是一门真正面向对象的语言,也没有内建的类(class)概念。但是,使用表(tables)和元表(metatables)可以很容易地创建自己的类系统。

简单的基于元表的类

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` 元方法的元表。当一个值被像函数一样调用时,就会触发 `__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` 变量已经作为上值(upvalue)存储在方法中,所以调用它的代码不需要再传递它。

继承也以这种方式实现

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

基于表 vs. 基于闭包的类

基于表的优势

基于闭包的优势

另请参阅


RecentChanges · preferences
编辑 · 历史
最后编辑于 2015 年 5 月 18 日 晚上 10:47 GMT (差异)