面向对象教程 |
|
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
基于表的优势
基于闭包的优势