使用Lua的table模拟面向对象有一个问题就是没法设置私有变量和私有方法,这样在团队开发的时候很可能造成一些不能修改的值被修改的问题。最好的解决办法是通过代码管理、新人培训、CodeReview来解决这个问题而不是通过Lua的语法来封装成私有。但是要做还是可以做到的。
第一种,整个类是直接使用的单例,把私有变量和方法设置为local就行。
local MyClass = {
publicValue = "public"
}
local privateValue = 0
local function PrivateFunc()
print("This is a private function")
privateValue = privateValue + 1
print("call private function "..privateValue.." times")
end
function MyClass:PublicFunc()
print("This is a public function")
print("you can call privateFunc from here")
PrivateFunc()
end
return MyClass
调用:
local MyClass = require "MyClass"
MyClass:PublicFunc()
print(MyClass.publicValue)
MyClass:PrivateFunc() --无效
print(MyClass.privateValue) --无效
这一种是有一定使用价值的,没有理解难度,对性能也没有造成任何影响。事实上这就是Lua5.1以后引入的最重要的模块机制。缺点就是一定只能单例使用,不能把MyClass看做类实例化出多个对象使用。
第二种,将MyClass看做类,使用的是“实例化”的对象。
local MyClass = {
}
--存放各个对象的私有变量
local private = {}
setmetatable(private, {__mode = "k"}) --必须让private变成weak表
function MyClass:New(name)
local privateTb = {
privateValue = name,
PublicPrint = MyClass.PublicPrint,
CallPrivatePrint = MyClass.CallPrivatePrint,
--这里放成员方法和私有变量
}
local newObject = setmetatable(
{
publicValue = "public",
--这里放公有变量
},
{
__index = function(tb,key)
return privateTb[key]
end,
__newindex = function(tb, key, val)
if privateTb[key] ~= nil then
print("can not modify private value")
return
end
rawset(tb,key,val)
end})
private[newObject] = privateTb
return newObject
end
--Public
function MyClass:PublicPrint()
print("This is a public function")
end
--Private
local function PrivatePrint(self)
print("This is a private function")
print("private value is "..private[self].privateValue)
end
--Public
function MyClass:CallPrivatePrint()
PrivatePrint(self)
end
return MyClass
调用:
local MyClass = require "MyClass"
local obj = MyClass:New("aaa")
obj.publicValue = "aaa public"
print(obj.publicValue)
obj.privateValue = "aaa private" --无效,修改被阻止
print(obj.privateValue) --还是原来的值
obj:PublicPrint()
obj:CallPrivatePrint()
local obj2 = MyClass:New("bbb")
obj2:CallPrivatePrint()
obj2:PrivatePrint()
第二种方法在第一种的基础上新增了New方法,因为各个新对象必须有自己独一份的私有变量,所以用privateTb存储,并在local变量private中进行保存。这里需要注意的点是private必须是key弱表。对于弱表很多人可能不太理解,简单来说就是设置了__mode元表的表(有三个值“k”、"v"、“kv”,分别代表key弱引用,value弱引用,key和value均为弱引用),其引用不会被垃圾管理器计数,如果只有弱表有引用那么依然会被垃圾回收。很显然如果private不为弱表的话,由于key是所有的对象,那么意味着这些对象都无法被回收。
第二种方法实现了私有变量不被修改,私有方法无法访问。实际上__index可以直接使用privateTb,但是为了外部无论如何都无法修改私有变量,改用了函数进行返回。这样即使getmetatable也无法修改私有变量。但是为了不让新的同名变量覆盖私有变量,必须设置__newIndex,并且判断该变量是否在privateTb中,由于设置新的变量都会访问__newIndex,无疑带来了额外的性能消耗。而访问私有变量也必须通过private表,对代码理解上也增加了成本。因此若无必要不需要封装到这个地步。