一、元表、元方法
元表:metatable
元方法:metamethod
自定义table的行为,如table加法操作。
元表metatable
t, t1, t2 = {}, {}, {}print(getmetatable(t)) --> nil,获取元表setmetatable(t, t2) --> t设置元表为t2,任何表都可以是其他表的元表setmetatable(t1, t2) --> t、t1共享元表t2,类似父类,描述共同行为setmetatable(t2, t2) --> 元表是自己,描述私有行为
元方法(关系运算符)
local mt = {-- 隐藏Set的元表,无法被外部getmetatable或者setmetatable-- getmetatable时,返回__metatable值-- setmetatable时,报错__metatable = "not your business"----------------关系运算符的元方法------------------ 只有下面三种:小于和等于的组合排列。为什么?见下面解释。__lt = function(a, b) end -- a < b 时触发,类型不同会报错__eq = function(a, b) end -- a == b时触发,必须有相同的元表,否则不会触发__le = function(a, b) end -- a <= b时触发,类型不同会报错--其他关系运算可被等价转换为上面的运算--a > b 等价于 b < a,有人说这不废话嘛,但实际触发的元方法就不一样了。--a >= b 等价于 b <= a--a ~= b 等价于 not(a == b)--!!注意a>b不等价于not(a<=b),偏序的时候,a>b和a<=b都可能不成立,比如两个不相见交的集合。--综上原因,三个足矣。}----------------关系运算符定义技巧------------------ a < b 转换为:a <= b and not(b <= a)-- a == b 转换为:a <=b and b <=a-- 这样唯一需要定义的就是__le元方法。mt.__le = function (a,b) -- set containmentfor k in pairs(a) doif not b[k] then return false endendreturn trueendmt.__lt = function (a,b)return a <= b and not (b <= a)endmt.__eq = function (a,b)return a <= b and b <= aend
元方法(算术运算符)
local mt = {----------------算术运算符的元方法------------------ Lua选择metamethod的过程:-- a + b时,先看a有没有,再看b有没有,如果都没有就报错。__add = function(a, b) end -- a + b时触发__mul = function(a, b) end -- a * b时触发__sub = function(a, b) end -- a - b时触发__div = function(a, b) end -- a / b时触发__unm = function(a) end -- -a 时触发__pow = function(a, b) end -- a^b 时触发__concat = function(a, b) end -- a..b 时触发}
元方法(lua库定义)
local mt = {----------------lua库定义的元方法----------------__tostring = function(a) end -- print(a)时触发,print触发a.tostring__index = function(a, b) end -- a.b,b不是a的域时触发,实现继承__index = parent -- 同上,a.b等价于parent.b,这里也可能再触发__index--rawset(a,b) 等价于a[b],不触发__index__newindex = function(a, b) end --a.b = 1,b不是a的域时触发__newindex = parent --同上,a.b=1 等价于 parent.b=1--rawset(t,k,v)等价于t[k]=v,不触发__newindex}
有默认值的表
local key = {} -- lua技巧,空表是唯一key的绝佳选择,任意创建的两个空表都不相等。local mt = {__index = function (t) return t[key] end}function setDefault (t, d)t[key] = dsetmetatable(t, mt)end
监控表
为表添加一个中间代理(空表)即可。
-------------监控表使用-------------local test = {}test = track(test) --对test域的访问和赋值都将被监控-------------监控表实现--------------- 为表设置代理proxy,对proxy的访问就是对表的访问。-- proxy一直为空,即可监控所有访问和赋值-- 触发的元方法,又实际操作的是表的值local index = {} -- 唯一keylocal mt = {__index = function (t,k) --监控域访问return t[index][k]end__newindex = function (t,k,v) --监控域赋值t[index][k] = vend}function track (t) --为t返回一个代理:空表。local proxy = {}proxy[index] = tsetmetatable(proxy, mt)return proxyend
只读表
同监控表原理。
-------------只读表使用-------------days = readOnly{"Sunday", "Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday"}print(days[1]) --> Sundaydays[2] = "Noday" --> 报错:stdin:1: attempt to update a read-only table-------------只读表实现-------------function readOnly (t)local proxy = {} --中间代理local mt = {__index = t,__newindex = function (t,k,v)error("attempt to update a read-only table", 2)end}setmetatable(proxy, mt)return proxyend
二、面向对象设计
Account = { a = "100" }function Account.fuck(self, b)print(b)endfunction Account:go()print(self.a)endAccount.fuck(100) --> 100Account.go() --> 100
继承
元方法__index = table实现。这样的话只能指定一个table(父类)
Account = {balance = 0}function Account:new (o)o = o or {}setmetatable(o, self)self.__index = selfreturn oendfunction Account:deposit (v)self.balance = self.balance + vendfunction Account:withdraw (v)if v > self.balance then error"insufficient funds" endself.balance = self.balance - vend
多重继承
元方法__index = function,通过函数可以返回不同的table父类,达到多父类继承。
-- look up for `k' in list of tables 'plist'local function search (k, plist)for i=1, table.getn(plist) dolocal v = plist[i][k] -- try 'i'-th superclassif v then return v endendendfunction createClass (...)local c = {} -- new class-- 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 instancesc.__index = c-- define a new constructor for this new classfunction c:new (o)o = o or {}setmetatable(o, c)return oend-- return new classreturn cend
成员私有
把对象拆分成:数据 + 操作。利用闭包,只需提供操作接口,即可调用数据。
function newAccount (initialBalance)local self = {balance = initialBalance}local withdraw = function (v) --闭包,操作接口self.balance = self.balance - vendlocal deposit = function (v) --闭包,操作接口self.balance = self.balance + vendlocal getBalance = function () return self.balance endreturn {withdraw = withdraw,deposit = deposit,getBalance = getBalance}end
单例
function newObject (value)return function (action, v)if action == "get" then return valueelseif action == "set" then value = velse error("invalid action")endendend使用起来很简单:d = newObject(0)print(d("get")) --> 0d("set", 10)print(d("get")) --> 10
实战代码
local LayerInGame = class("LayerInGame", function() return display.newLayer() end)-- 或者local LayerInGame = class("LayerInGame", cc.Layer)-- classname类名,...父类function class(classname, ...)local cls = { __cname = classname }-- 所有的父类local supers = { ...}-- 遍历所有的父类,将父类的特性附加给clsfor _, super in ipairs(supers) dolocal superType = type(super)-- 父类必须是nil或者table或者functionassert(superType == "nil" or superType == "table" or superType == "function",string.format("class() - create class \"%s\" with invalid super class type \"%s\"",classname, superType))-- 父类如果是函数if superType == "function" then-- __create创建函数不能重复assert(cls.__create == nil,string.format("class() - create class \"%s\" with more than one creating function",classname));-- if super is function, set it to __create-- 直接将函数赋值给创建函数cls.__create = superelseif superType == "table" thenif super[".isclass"] then-- 父类是一个cocos2dx的类,node,layer,spriteassert(cls.__create == nil,string.format("class() - create class \"%s\" with more than one creating function or native class",classname));-- 将父类的构建函数赋值给自己cls.__create = function() return super:create() endelse-- 一个普通的Lua table-- 将所有的Lua table型父类保存在__supers中cls.__supers = cls.__supers or { }cls.__supers[#cls.__supers + 1] = super-- 只将第一个父类赋值给superif not cls.super then-- set first super pure lua class as class.supercls.super = superendendelseerror(string.format("class() - create class \"%s\" with invalid super type",classname), 0)endend-- 上面是解决了创建的问题,下面解决访问的问题-- __index使得cls具备父类的属性cls.__index = clsif not cls.__supers or #cls.__supers == 1 then-- 父类是普通的Luatablesetmetatable(cls, { __index = cls.super })else-- 遍历super一次访问父类的域,找到为止setmetatable(cls, {__index = function(_, key)local supers = cls.__supersfor i = 1, #supers dolocal super = supers[i]if super[key] then return super[key] endendend} )end-- 默认构造函数if not cls.ctor thencls.ctor = function() endend-- 创建cls,并调用默认构造函数cls.new = function(...)local instanceif cls.__create theninstance = cls.__create(...)elseinstance = { }endsetmetatableindex(instance, cls)instance.class = clsinstance:ctor(...)return instanceend-- 另一种构造函数cls.create = function(_, ...)return cls.new(...)endreturn clsend
