面向对象的三个基本特征
- 封装:隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别。
- 继承:使子类具有父类的属性、方法或者重新定义、追加属性和方法等。
- 多态:同以操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类的方法。
Lua中的面向对象
Lua中的表和对象有很多相似性,因此在Lua中可以使用表来实现面向对象。
Lua中类的方法可以通过表+function来模拟:
zhangsan = {name = ""} function zhangsan.setName(self, name) self.name = name end zhangsan.setName(zhangsan, "张三") print(zhangsan.name) --输出: --张三
类(Class)
上面的例子中,zhangsan对象拥有了属性和方法,但是这个对象却不是通过类来创建的。在大多数面向对象的语言中都提供了类的概念,类在对象创建中扮演了模板的作用。
在Lua中,我们可以使用原型的思想来模拟类。让每一个对象都有一个原型,当对象遇到未知的操作时就在原型中去查找。
如下,为zhangsan指定它的原型Person:
Person = {} function Person.setName(self, name) self.name = name end function Person:new(person) person = person or {} self.__index = self setmetatable(person, self) return person end zhangsan = Person:new() print(zhangsan.name) zhangsan:setName("张三") print(zhangsan.name) --输出: --nil --张三
在上面的代码中,使用了冒号:来操作,如Person:new(person)这样的写法相当于Person.new(Person, person),冒号只是一个语法糖,这个函数中的self即为Person类本身。
继承
在类(Class)的new操作中,self.__index = self,将Person的元方法__index指向了自身,这样做会导致当调用一个方法时,解释器第一时间将会查找Person自身有没有这个方法。这一点和继承很相似。
在继承中,子类可以拥有父类的属性和方法,也可以定义自己的属性和方法。
如果要从一个类中派生一个子类,可以先创建一个从基类继承了所有操作的空类:
Male = Person:new() Male.sex = "男" zhangsan = Male:new() zhangsan:setName("张三") print(zhangsan.name, zhangsan.sex) --输出: --张三 男
Male拥有了Person的new方法,同时通过Male创建的zhangsan拥有Person的setName方法。
在这个过程中,Male = Person:new()会将Male的元表设置为Person,zhangsan = Male:new()则将zhangsan的元表设置为了Male。
当调用zhangsan:setName时,Lua将首先从zhangsan查找setName方法,发现不存在时,则从zhangsan元表Male的元方法__index中(即Male本身)查找,直到在Person中找到该方法。因此,zhangsan不仅有Male的sex属性,还拥有Person的setName方法。
同时,还可以很方便地在Male中重写Person的setName方法,只需要定义一个新方法即可。
function Male:setName(name) self.name = "Male:"..name end zhangsan = Male:new() zhangsan:setName("张三") print(zhangsan.name) --输出: --Male:张三
这是由于,在Male中已经有setName方法了,所以解释器不会再去Person中去查找。