Lua面向对象编程
来源: http://blog.csdn.net/vermilliontear/article/details/50650883
Q:如何定义对象的方法以及调用对象的方法?
A:面向对象的特殊性在于它以this
指针的方式传递了对象本身,并且这种操作是隐藏起来的。
在Lua中使用:
实现面向对象方式的调用。:
只是一个语法糖,它同时在方法的声明与实现中增加一个名为self的隐藏参数(对象本身)。
Account = {balance = 1000} -- 账户余额初始为1000。 --[[ 取钱。 使用面向对象的方式隐藏了"self"参数, "withdraw()"完整的参数列表是"Account.withdraw(self, v)"。]] function Account:withdraw(v) self.balance = self.balance - v end --[[ 使用面向对象的方式隐藏了"self"参数, 实际传递给"withdraw()"的参数是"Account"和"100.00"。]] Account:withdraw(100.00) print(Account.balance) --> 900.0
我们可以用.
定义函数,而用:
调用函数,或者反之亦然,只要我们正确的使用这个被隐藏的self
参数。
Account = {balance = 1000} -- 账户余额初始为1000。 function Account.withdraw(self, v) -- 使用"."定义函数。 self.balance = self.balance - v end Account:withdraw(100.00) -- 使用":"调用函数。 print(Account.balance) --> 900.0 -- 存钱。 function Account:deposit(v) -- 使用":"定义函数。 self.balance = self.balance + v end Account.deposit(Account, 600.00) -- 使用"."调用函数。 print(Account.balance) --> 1500.0
Q:如何实现类?
A:类在面向对象语言中就好象一个模板,通过模板所创建的实例就具有模板中规定的特性。Lua中没有类的概念,每一个对象规定自己的行为,每一个对象就是自己的实例。不过在Lua中模拟“类”并不难,我们可以用继承的概念,使用两个对象,让其中一个对象作为另一个对象的“类”, setmetatable(a, {__index = b}) -- b作为a的类,在a中找不到的方法都将去b中寻找。
继续扩展银行账户的例子,
Account = {balance = 0} -- 账户余额初始为0。 function Account:new (o) o = o or {} -- create object if user does not provide one setmetatable(o, self) -- 新对象的"metatable"是"Account"(或其子类)。 self.__index = self -- 新对象"metatable"中的"__index"依旧是"Account"(或其子类)。 return o end function Account:deposit(v) self.balance = self.balance + v end function Account:withdraw(v) self.balance = self.balance - v end --[[ "account1"继承了"Account"中的特性, 就好象"account1"是由"Account类"创建出来的一样。 这一句代码后,设置了"account1"的"metatable"是"Account", "Account.__index"依旧是"Account"。]] account1 = Account:new() -- 这里打印的是"Account.balance","Account.balance"作为"Account"实例的默认值。 print(account1.balance) --> 0 print(Account.balance) --> 0 --[[ "account1:deposit(500.00)",实际上调用的是"account1.deposit(a, 500.00)", "account1"中没有"deposit()",所以去找"account1"的"metatable"中的"__index", 即"getmetatable(account1).__index",即"Account"。 又因为"Account"是一个"table",所以"account1.deposit(a, 500.00)" 相当于"Account.deposit(a, 500.00)"。]] account1:deposit(500.00) --[[ 这里打印的是"account1.balance", 因为在"Account.deposit()"中为"self.balance"赋值的同时定义了"account1.balance"。]] print(account1.balance) --> 500 print(Account.balance) --> 0
Q:如何实现继承?
A:上面的例子中已经初步展现了继承的方式,下面再使用一个更贴切的例子进行说明,
Account = {balance = 0} function Account:new (o) o = o or {} setmetatable(o, self) self.__index = self return o end function Account:deposit (v) self.balance = self.balance + v end function Account:withdraw (v) -- 普通账户不可以透支。 if v > self.balance then error"insufficient funds" end self.balance = self.balance - v end --[[ 信用卡账户继承自普通账户,可以透支。 "CreditAccount"是"Account"的一个“实例”, 但他同时也可以作为一个“类”以产生其他实例。]] CreditAccount = Account:new{limit = 1000.00} -- 为了让信用卡账户能够透支,需要重写"withdraw()"方法。 function CreditAccount:withdraw (v) -- 信用卡账户在一定额度内可以透支。 if v - self.balance >= self:getLimit() then error"insufficient funds" end self.balance = self.balance - v end function CreditAccount:getLimit () return self.limit or 0 end --[[ 新的实例也可以规定自己的限额。 "CreditAccount"中没有"new()",实际调用的是"Account.new()"。]] creditaccount1 = CreditAccount:new{limit = 2000.00} --[[ "creditaccount1"中没有"deposit()", "CreditAccount"中没有"deposit()",实际调用的是"Account.deposit()"。]] creditaccount1:deposit(100.00) -- 此时调用的是"CreditAccount:withdraw()"。 creditaccount1:withdraw(200.00) print(creditaccount1.balance) --> -100.0
Q:如何实现多重继承?
A:将__index
赋值为一个函数,函数中可以搜索想要找的表(“父类”)。
还记得吗,当__index
是个函数时,Lua调用它,以”table”和缺失的”key”作为参数。而当__index
是一个”table”时,Lua直接以缺失的”key”作为它的”key”再次访问他(相当于拿着缺失的”key”在它这张”table”中寻找)。
------------------- "Account类" ------------------- Account = {balance = 0} function Account:new (o) o = o or {} setmetatable(o, self) self.__index = self return o end function Account:deposit (v) self.balance = self.balance + v end function Account:withdraw (v) if v > self.balance then error"insufficient funds" end self.balance = self.balance - v end -------------------------------------------------- -- look up for 'k' in list of tables 'plist' local function search(k, plist) for i = 1, #plist do local v = plist[i][k] -- try 'i'-th superclass if v then return v end end end function createClass(...) local c = {} -- new class for i = 1, select('#', ...) do -- arg = {[1] = Account, [2] = Named}。 arg[i] = select(i, ...) end --[[ class will search for each method in the list of its parents ('arg' is the list of parents)]] setmetatable(c, {__index = function (t, k) return search(k, arg) end}) -- prepare 'c' to be the metatable of its instances c.__index = c -- define a new constructor for this new class function c:new (o) o = o or {} setmetatable(o, c) return o end -- return new class return c end Named = {} -- 姓名类,记录账户的姓名。 function Named:getname () return self.name end function Named:setname (n) self.name = n end -- 创建一个新类"NamedAccount",其继承于"Account"与"Named"。 NamedAccount = createClass(Account, Named) account = NamedAccount:new{name = "Paul"} --[[ "account"中没有"getname", 所以找"account"的"metatable.__index", "metatable"是"NamedAccount", "NamedAccount"的"__index"还是"NamedAccount"。 而"NamedAccount"中依旧没有"getname()", 所以找"NamedAccount"的"metatable.__index",是个函数, 所以Lua调用这个函数,并将"NamedAccount"和"getname"作为参数传入。 函数中又调用"search()","search()"中首先在"Account"中找"getname", 没找到,又在"Named"中找"getname",找到了,所以调用"getname()"。]] print(account:getname()) --> Paul
多重继承的效率可能比单一继承的效率低很多(因为多了search()
这个过程)。如果想提高效率的话可以将找到的父类的函数拷贝到子类中,
setmetatable(c, {__index = function (t, k) local v = search(k, arg) t[k] = v -- "Named.getname()"存储在了"account"中。 return v end})
这样,除了第一次调用,之后的调用实际上是调用子类中拷贝过来的父类的函数,省去了search()
的过程,效率就高很多。但是这种做法也有缺陷,在程序运行起来之后,就很难改变父类中的方法了,因为即使更改了,子类也不会继承过去(子类保存了父类原先的方法)。
Q:如何定义私有成员或私有方法?
A:使用两个”table”,其一存储私有成员,另一个存储公有成员和公有方法,两个”table”组成”Closure”,私有”table”作为公有”table”的”Closure”被访问,私有方法直接存储在”Closure”中,
function newAccount (initialBalance) -- 私有"table"。 local self = { balance = initialBalance, -- 余额。 count = 0 -- 积分。 } -- 私有方法,未导出到公有"table"中,外部无法访问。 local addCount = function (v) self.count = self.count + v * 0.1 -- 消费的10%作为积分。 end local withdraw = function (v) self.balance = self.balance - v addCount(v) end local deposit = function (v) self.balance = self.balance + v end local getBalance = function () return self.balance end local getCount = function () return self.count end -- 公有"table"。 return { withdraw = withdraw, deposit = deposit, getBalance = getBalance, getCount = getCount } end account = newAccount(100) print(account.balance) --> nil print(account.count) --> nil print(account.getBalance(), account.getCount()) --> 100 0 account.deposit(500) print(account.getBalance(), account.getCount()) --> 600 0 account.withdraw(100) print(account.getBalance(), account.getCount()) --> 500 10
附加:
1、Lua中的”table”与面向对象编程中的对象有很多相似的地方。像对象一样,”table”有成员变量;像对象一样,”table”本身与其存储的值相互独立,特别在于两个不同的对象拥有相同的值,但他们依旧是不同的对象。反之,一个对象在不同的时间可以有不同的值,但对象本身不会改变;像对象一样,”table”的生命周期与其被谁创建以及在哪儿被创建无关。
2、Lua中的面向对象编程有意思的地方在于,当你需要增加新的方法时,既可以在“类”中添加,也可以在“实例”中直接添加。
-- 以添加一个“增加积分”的方法为例。 -- 在“类”中添加,适用于许多用户均需要该方法。 function CreditAccount:addCount(count) ... end -- 在“实例”中直接添加,适用于少数用户需要该方法。 creditaccount1 = CreditAccount:new() creditaccount1.addCount = function(self, count) ... end
3、Lua使用”table”表示对象,本身不提供私有性机制。Lua的设计初衷是针对于中小型程序,通常他们是一个大型系统的一部分。因此Lua避免设计上的冗余以及过多的人为限制,如果对象中的某些成员你不该访问,那么你最好就”just do not do it”。Lua提供给程序员“meta-mechanisms”,程序员可以通过他们模拟许多需要的特性。