简介
Middleclass is an object-oriented library for Lua.
If you are familiar with Object Orientation in other languages (C++, Java, Ruby … )
then you will probably find this library very easy to use.
The code is reasonably readable, so you might want to employ some time in going through it,
in order to gain insight of Lua’s amazing flexibility.
使用
原理
类似其他面向对象语言一样,要想封装某个对象,要先定义一个类,使用middleclass我们可以用如下方式实现:
local class = require("middleclass")
local Test = class("test")
- 首先使用
require加载了middleclass.lua文件,返回的class本身是一个table对象,这个table的元表实现了__call方法,(关于此元方法的作用,可去此处查看)。所以我们可以像调用function那样去使用那个table。
local middleclass = {
_VERSION = 'middleclass v4.1.1',
_DESCRIPTION = 'Object Orientation for Lua',
_URL = 'https://github.com/kikito/middleclass',
_LICENSE = [[
--- 此处省略了一些字符
]]
}
-- 此处省略中间代码
function middleclass.class(name, super)
assert(type(name) == 'string', "A name (string) is needed for the new class")
return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
end
setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
-- 为middclass设置元表,并定义相应的__call元方法
return middleclass
接下来就是_createClass方法,此方法是根据我们提供的名字name创建对应的类。
local function _createClass(name, super)
local dict = {}
dict.__index = dict
local aClass =
{ --次数变量包括方法,table等
name = name, --类名
super = super, --直接父类
static = {}, --存储静态变量,类的实例不可访问
__instanceDict = dict, --存储实例变量,也就是该类创建的对象可以访问的变量
__declaredMethods = {}, --保存每个类中自定义的变量,当给某个父类增加了新的变量,他会检查子类的这个表中
--是否定义类相同的变量,若没有,则将此变量添加到子类的实例变量表中(__instanceDict)
--是实现继承的关键因素
subclasses = setmetatable({}, {__mode='k'})
--存储该类的子类,是个键为弱引用的若引用表
}
if super then
setmetatable(aClass.static, {
__index = function(_,k)
local result = rawget(dict,k)
if result == nil then
return super.static[k]
end
return result
end
})
else
setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
end
setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
__call = _call, __newindex = _declareInstanceMethod })
return aClass
end
此方法很巧妙,将实例方法和静态方法分开存储,以便于该类的实例对象不可以调用静态方法(具体原因往下看)。
- 首先定义了一个aClass的table,table中定义了几个变量,如上面注释所示。
- 先看一下super为nil的情况,aClass的元表的__index元方法为aClass.static,aClass.static元表的__index元方法为dict。也就是通过类名既能访问类中静态变量,又可以访问实例变量(这与其他面向对象的语言中的类可能有所不同)
- 然后是super部位nil的情况,此时aClass.static元表的__index为一个方法,当aClass中不存在某个值时,首先会去aClass.static中查找,aClass.static中找不到会就去其父类super的static表中查找,找到则返回,若找不到并且假如super上面还有父类super,则会从super的父类的static表中查找,这样一直递归下去,直到找到最终的父类,若最终父类的static表中没有,便会查找dict表,若还没有则返回nil。
接下来需要看一下aClass的__newindex元方法。
local function _declareInstanceMethod(aClass, name, f)
aClass.__declaredMethods[name] = f
if f == nil and aClass.super then
f = aClass.super.__instanceDict[name]
end
_propagateInstanceMethod(aClass, name, f)
end
local function _propagateInstanceMethod(aClass, name, f)
f = name == "__index" and _createIndexWrapper(aClass, f) or f
aClass.__instanceDict[name] = f
for subclass in pairs(aClass.subclasses) do
if rawget(subclass.__declaredMethods, name) == nil then
_propagateInstanceMethod(subclass, name, f)
end
end
end
这两段代码主要是为了将父类的方法同步到子类。
- 首先aClass中新增的变量(可以是任何对象,如表,方法等),会被添加到存储实例变量的表
__instanceDict中,该表中变量,类的实例都可以访问。 - 同时变量也会被记在
__declaredMethods表中,这个表主要存储类中自定义(也就是在这个类中定义的,不是继承过来的)的变量,也是同步父类变量和子类变量的关键,假设存在两个类A,B。A是B的父类,假设先B调用B[1] = 2,这是在B的__declaredMethods便会存在key为1,value为2的键值对,若在在A中添加A[1] = 3,则不需要将key为1,value为3的键值对同步到B中,此时调用print(B[1])放回2. - 若在A中添加A[2] = 4,则key为2,value为4的键值对,会同步到B的存储实例变量的表
__instanceDict中,这时调用print(B[2])返回4 _createIndexWrapper(aClass, f),此方法只有在为类增加元方法__index,时才会调用,也就是类的实例中没有定义某个变量,可以从此方法中查找,例如
local class = require("middleclass")
local Test = class("test")
function Test:initialize(...)
print(...)
end
local c = 0
function Test:tam()
c = c + 1
print(c)
end
local test = nil
function Test:get_instance()
if test == nil then
test = Test:new()
end
return test
end
return Test
local Test = require("Test")
local t1 = Test:get_instance()
t1:tam()
print(t1.k)
Test["__index"] = {k = 5,t = 6}
print(t1.k)
-- 1
-- nil
-- 5
- 此方法类似与middlecalss提供的
include方法,只不过include更加全面,不仅可以加添实例变量,也可以添加静态变量;而使用__index添加的变量,类的实例都可以访问。 - 推荐使用
include
include
include = function(self, ...)
assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
return self
end
其本身没有什么,主要是_includeMixin方法,此方法将表mixin中定义的变量添加到类中,作为默认方法。
local function _includeMixin(aClass, mixin)
assert(type(mixin) == 'table', "mixin must be a table")
for name,method in pairs(mixin) do --当前表
if name ~= "included" and name ~= "static" then aClass[name] = method end
end
for name,method in pairs(mixin.static or {}) do
aClass.static[name] = method --把 key,value 设置进元表中
end
if type(mixin.included)=="function" then mixin:included(aClass) end
return aClass
end
- 实例方法添加进存储实例的表中
- 静态方法存入存储静态变量的表中
- 当mixin中定义了名为
included的方法时,首次调用include方法也会调用mixin中定义的included方法,可以作为Action使用。
local Test = require("Test")
Test:include({ key = 2222,included = function(self,class) print(self.key) end})
-- 2222
DefaultMixin
这里面存储了每个类都会包含的静态方法和实例方法,以比较重要的几个为例。
allocate = function(self)
assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
return setmetatable({ class = self }, self.__instanceDict)
end,
new = function(self, ...)
assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
local instance = self:allocate()
instance:initialize(...)
return instance
end,
假设定义了一个Fruit类,创建该类的一个实例
local f = Fruit:new()
通过上面代码,新生成的实例f的元表为类Fruit中定义的表__instanceDict,所以通过类的实例也就没有办法访问类中定义的静态变量(也即是static表中存储的变量)。
subclass = function(self, name)
assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
assert(type(name) == "string", "You must provide a name(string) for your class")
local subclass = _createClass(name, self)
for methodName, f in pairs(self.__instanceDict) do
_propagateInstanceMethod(subclass, methodName, f)
end
subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end
self.subclasses[subclass] = true
self:subclassed(subclass)
return subclass
end,
此方法为定义一个继承自该类的子类。
- 首先通过_createClass创建子类
- 在通过_propagateInstanceMethod方法,将父类方法,同步到子类
- 最后将子类引用,保存到父类的subclasses表中。当父类增加了某个变量时,可以通过遍历此表,找出每个子类,并将新定义的变量同步到子类中。
总结
该案例大致包含这些内容,详细使用,可自行去github上查看教程。