其实脚本语言已经封装的很好了,用到的一切都可以说是对象了,单独学习一下lua的OOP只是为了将来看代码方便一点。
首先需要注意的:
1.lua不存在类的概念,没有明确的语法用来声明类
2.lua的OOP实际上是通过table来实现的,这也有道理,因为对象本质就是数据和操作封装在一起
一.对象
上面提过,对象本质就是数据和操作的集合,通过表的键值对很容易定义一个对象。下面通过《Programming in Lua》中的例子来讲解。先吐槽一下这本书,由lua的作者Roberto Ierusalimschy编写,所以每个知识点都很到位。但是也正因为他太熟悉这门语言,所以知识递进做的不是很好,比如在类中用到了元表metatable的知识,但是你去学习metatable的时候又出现了类的概念。最好的做法是尽量避免知识重叠,实际上metatable不太需要那么复杂的例子,所以这本书不太适合新手看。
1 Account = {balance = 0} 2 function Account.withdraw (v) 3 Account.balance = Account.balance - v 4 end
上面的代码定义了一个Accoount的对象,有一个属性balance和一个方法withdraw,这样看起来已经实现了对象——数据和操作的封装,但是如果改变了对象的名字,那么就会出现下面的问题
1 a = Account; Account = nil 2 a.withdraw(100.00) -- ERROR!
这是因为我们在定义withdraw函数的时候使用了Account这个变量的名字,而不是对象本身,这显然违反了对象的原则,对象的数据和操作只和值有关,而和变量名无关,因此需要在定义方法的时候指明操作的对象,这需要在方法的第一个参数加一个self,用来传递对象本身,如下
1 function Account.withdraw (self, v) 2 self.balance = self.balance - v 3 end
这样在调用的时候将对象传递进去,就知道改变的是哪个对象了
1 a1 = Account; Account = nil 2 ... 3 a1.withdraw(a1, 100.00) -- OK
当然,这个其实没有什么神奇之处,只是额外传递了一个参数,但是lua像其他的OOP语言一样,提供了将self隐藏的方法,使用冒号声明函数,这样我们在定义和使用函数的时候就不必显式的加入self参数,方法如下
1 function Account:withdraw (v) 2 self.balance = self.balance - v 3 end
调用的时候也是对象:方法,这样就省去了额外写一个self的麻烦,但本质和上面的做法是一样的,只不过语言替你处理了而已
a:withdraw(100.00)
二.类和继承
上面说过lua之中并没有真正的类声明,所以一切都是用table机制实现的,但是仔细阅读《Programming in Lua》这个本书,就会发现这里面的类其实并不是严格意义上的类,而是继承类(派生类),比如下面这段代码
1 Account = {balance = 0} 2 function Account:new (o) 3 o = o or {} 4 setmetatable(o, self) 5 self.__index = self 6 return o 7 end
实际上lua的继承是使用了元表的__index元方法,不了解的可以看这篇Lua学习笔记-metatable元表
那么当你使用Account:new出来的对象,实际上就是将Account设置成了元表的一张普通表,所有它找不到的属性和方法,都会去元表中查找,间接实现了继承。为什么我说是实现了继承而不是类呢,因为像C++和Java这种语言的类,在对象实例化之后是不可能私自添加属性或方法的,但是lua中对象实际就是一个table,你想加什么就加什么,没有这些限制,所以更像继承。
看到这里,你会发现lua的类和对象概念十分模糊,实际上大多数的脚本语言并不会经常使用这些概念,因为你使用的每一个值都是一个对象,你以为你声明了类,实际上他也是一个对象,只不过用起来好像类一样。那么为什么还要学习呢,其实重要的是思想,为什么使用类和对象,肯定离不开封装、继承、多态这三大特性,而大多数脚本语言本身就很好的实现了这些,重点关心的应该是怎么用好。