Lua面向对象编程学习笔记
其实 lua 中的 table 是一种对象,因为它跟对象一样,有其自己的操作方法:
role = { hp = 100 }
function role.addhp(hp)
role.hp = role.hp + hp
end
role.addhp(50)
print(role.hp)
上面代码创建了一个名为 role 对象,并有一个 addhp 的方法,执行 "role.addhp" 便可调用 addhp 方法。
不过上面对象 role 是以全局变量的方式创建,会有一种“全局污染”的威胁,即变量 role 在其他地方被重新赋值(例如被赋成 nil),对象里的属性或方法可能会面临被销毁或不能正常工作的情况。
对于这种问题,lua 提供一种“接受者”的解决方法,即额外添加一个参数 self 来表示对象本身:
role = { hp = 100 }
function role.addhp(self, hp)
self.hp = self.hp + hp
end
r = role
r.addhp(r, 50)
print(r.hp)
这样就不怕对象 role 被“全局污染”,因为构造了一个子对象 r,并以参数的方式传入,以供其方法调用操作。
对于这种把对象本身以参数的方式传入对象方法里的写法,lua 提供了一种更优雅的写法,把点号(.)替换为冒号(:),这样在方法定义或调用时,便可隐藏 self 参数。修改如下:
role = { hp = 100 }
function role:addhp(hp)
self.hp = self.hp + hp
end
r = role
r:addhp(50)
print(r.hp)
上面的 "r.addhp(50)" 的写法等价于 "r.addhp(r, 50)"
类
lua 没有类的概念,不过可以通过元表(metatable)来实现与原型 prototype 类似的功能,而 prototype 与类的工作机制一样,都是定义了特定对象行为。lua 里的原型特性主要使用元表的 __index 事件来实现,这样当调用对象没定义的方法时,会向其元表的 __index 键(事件)查找。例如有 a 和 b 两个对象,想让 b 作为 a 的原型 prototype,只需要把 b 设置为 a 元表的 __index 值就行:
setmetatable(a, {__index = b})
这样,当对象 a 调用任何不存在的成员都会到对象 b 中查找,a 可以拥有或调用 b 的属性或方法,从某种意义上看,b 可以看作是一个类,a 是 b 的对象。
对于上面 role 的例子,对象的创建可以用 __index 元方法来改写,这样新创建的对象就拥有和 role 一样的属性和方法。
function role:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
当执行 "r = role:new() " 创建一个对象时,r 将 role 设置为自己的元表,那么调用 "r:addhp(50)" 的时候,会在 r 里查找 addhp 方法,如果没有找到,则会进一步搜索其元表的 __index,因此等价于:
getmetatable(r).__index.addhp(r, 50)
从上面的 role:new 方法可以知道,role 的 __index 在创建时被指定为 self,因此其实就是执行:
role.addhp(r, 50)
完整的类例子:
role = { hp = 100 }
function role:new(o)
o = o or {}
setmetatable(o, self)
self.__index = self
return o
end
function role:addhp(hp)
self.hp = self.hp + hp
end
r = role:new()
r:addhp(50)
print(r.hp)
继承
lua 里继承机制还是像实现类那样实现。
假如打算从类 role 派生出一个子类 priest,它有一个魔法属性值 mp,那么可以先从类 role 构造一个 priest,继承类 role 的所有属性和方法:
priest = role:new()
虽然 priest 是 role 的一个实例,不过它具有类 role 的所有属性和方法,其实也可以把它看做是从类 role 派生出来的类,因此可以从类 priest 继续 new 一个对象出来:
p = priest:new({ mp = 100 })
上面实例 p 除了多出一个魔法属性值 mp 外,还继承类 role 的所有属性和方法,当调用 "p.addhp" 方法时,lua 在 p 中找不到 addhp 方法,会到 priest 中找,在 priest 中找不到,会到 role 中找。
因此,想重定义从父类 role 继承来的方法,在类 priest 上定义即可。假如想重定义 addhp 方法:每次加血都要先判断魔法值够不够,如果够,则加血,并扣除一定的魔法值。修改如下:
function priest:addhp(hp)
if self.mp >= 20 then
self.mp = self.mp - 20
self.hp = self.hp + hp
end
end
这样,当调用 "p:addhp" 时,lua 会优化取类 priest 定义的 addhp 方法。
推荐阅读
-
C#面向对象编程基础概念汇总
-
PHP面向对象三大特点学习(充分理解抽象、封装、继承、多态)
-
PHP学习记录之面向对象(Object-oriented programming,OOP)基础【类、对象、继承等】
-
PHP学习记录之面向对象(Object-oriented programming,OOP)基础【接口、抽象类、静态方法等】
-
JavaScript函数、闭包、原型、面向对象学习笔记
-
Java面向对象编程之类的继承详解
-
PHP 面向对象程序设计(oop)学习笔记(一) - 抽象类、对象接口、instanceof 和契约式编程
-
PHP 面向对象程序设计(oop)学习笔记 (二) - 静态变量的属性和方法及延迟绑定
-
PHP 面向对象程序设计(oop)学习笔记(三) - 单例模式和工厂模式
-
浅析Objective-C的程序结构及面向对象的编程方式