文章目录
- 一、面向对象
- 1. 特征
- 二、Lua 中面向对象
- 1. 类的封装
- (1) 创建对象
- (2) 访问属性
- (3) 访问成员函数
- 2. 类的继承
- 3. 类的多态
- 三、方法访问权限(私有公有)
一、面向对象
面向对象编程(Object Oriented Programming,OOP)是一种非常流行的计算机编程架构。
1. 特征
- 封装: 指能够把一个实体的信息、功能、响应都装入一个单独的对象中的特性。
- 继承: 继承的方法允许在不改动原程序的基础上对其进行扩充,这样使得原功能得以保存,而新功能也得以扩展。这有利于减少重复编码,提高软件的开发效率。
- 多态: 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
- 抽象: 抽象(Abstraction)是简化复杂的现实问题的途径,它可以为具体问题找到最恰当的类定义,并且可以在最恰当的继承级别解释问题。
二、Lua 中面向对象
Lua语言本身并没有提供面向对象的语法机制,这需要我们自己设计实现一套类的机制。首先,对于面向对象来说,我们至少需要类和对象这两个概念。
我们知道,对象由属性和方法组成。Lua中最基本的结构是table,所以需要用table来描述对象的属性。
Lua中的 function 可以用来表示方法。那么Lua中的类可以通过 table + function 模拟出来。
类至少包含一个用于构造对象的方法。 对应到Lua上,就是一个代表类的table,它有一个构造函数,返回代表该类对象的table:
Class = {}
function Class:new()
local o = {}
return o
end
local object = Class:new()
1. 类的封装
-- 元类
rect = { area = 0, length = 0, breadth = 0 }
-- 派生类的方法 new
function rect:new(o, length, breadth)
o = o or {}
setmetatable(o, self)
self.__index = self
self.length = length or 0
self.breadth = breadth or 0
self.area = length * breadth
return o
end
-- 派生类的方法 printArea
function rect:printArea()
print('rect\'s area = ', self.area)
end
(1) 创建对象
创建对象是为类的实例分配内存的过程。每个类都有属于自己的内存并共享公共数据。
r = rect:new(nil, 10, 20)
(2) 访问属性
我们可以使用点号(.)来访问类的属性:
print(r.length)
(3) 访问成员函数
我们可以使用冒号 : 来访问类的成员函数:
r:printArea()
内存在对象初始化时分配。
2. 类的继承
面向对象的一大特点就是继承,Lua的元表跟元方法也能模拟。
【Person】:
--[[
filename: '1_test.lua'
]]--
Person = {
age = 18,
name = 'cauchy',
}
function Person:new(age, name)
local o = {}
setmetatable(o, self)
self.__index = self
o.age = age
o.name = name
return o
end
function Person:getAge()
return self.age
end
function Person:printf()
print(self.age, self.name)
end
Teacher继承了Person,并重写了Print()方法。
【Teacher】:
--[[
filename: '2_test.lua'
]]--
require "1_test"
Teacher = Person:new()
function Teacher:new(age, name, course)
local o = Person:new(age, name)
setmetatable(o, self)
self.__index = self
o.course = course
return o
end
function Teacher:printf()
print(self.age, self.name, self.course)
end
local t = Teacher:new(30, 'Miss Liu', 'English')
t:printf()
print(t:getAge())
运行结果:
30 Miss Liu English
30
Teacher并没有重写GetAge()方法,然而 t1:GetAge() 却正确的输出了30,所以是正确的继承了这个方法。
而重写的printf()方法,多输出了一个course,也正常输出,可见重写也是可以的。
3. 类的多态
多态:同一个实现接口,使用不同的实例而执行不同的操作。
Object = {} -- 所有对象基类: Object
function Object:new() -- 实例化方法
local obj = {}
setmetatable(obj, self)
self.__index = self
return obj
end
function Object:create(cls) -- 继承
_G[cls] = {} -- 大G表通过键值对,存储所有的全局变量
local class = _G[cls]
setmetatable(class, self)
self.__index = self
class.base = self -- 设置base属性,方便子类找父类
end
--[[
1. 生成GameObject类,为Object子类。
(1) 两个属性:(posX, posY)
(2) 一个方法:(move())
--]]
Object:create("GameObject") -- create object (object -> GameObject)
GameObject.posX, GameObject.posY = 0, 0 -- set attribute
function GameObject:move() -- set method
print(string.format('move: (%s)', self))
self.posX = self.posX + 1
self.posY = self.posY + 1
print("posX: ", self.posX, " posY: ", self.posY)
end
--[[
多态
1. 生成Player类,为GameObject子类。
(1) 重写move()方法
--]]
GameObject:create("Player") -- create object (GameObject -> Player)
function Player:move() -- set method
--[[
这样调用即GameObject:move()
实例化出来的Player对象都共用GameObject的posX, posY。
--]]
-- self.base:move()
--[[
这样实际传入的self是Player
但是Player里没有posX, posY属性,会去找GameObject,赋值自己的posX, posY,不会相互影响。
--]]
self.base.move(self)
end
local p1 = Player:new()
local p2 = Player:new()
p1:move()
p2:move()
p1:move()
运行结果:
move: (table: 0x55fdb0457b70)
posX: 1 posY: 1
move: (table: 0x55fdb0457bb0)
posX: 1 posY: 1
move: (table: 0x55fdb0457b70)
posX: 2 posY: 2
三、方法访问权限(私有公有)
冒号的作用是省略了self的传递,也就是说,如果我们不想写冒号改为写等号的话,那么每个方法的第一个参数必然是self。
冒号的作用有两个:
1、方法定义:会增加一个额外的隐藏形参(self)
2、方法调用:会增加一个额外的实参(表自身)
local t = { a = 1, b = 2 }
function t:add() -- 使用 : 自定义函数
return self.a + self.b
end
function t.sub(self) -- 使用 . 自定义函数
return self.a - self.b
end
print(t:add())
print(t.add(t))
print(t.sub(t))
print(t:sub())
以上两种方法都可以说是Public方法。
local function Console(self) -- 私有方法 Console
print(self.a, self.b)
end
私有方法同样需要传递这个self,区别在于,私有方法是用local写的。跟其他语言不同之处在于,Lua中模拟的私有方法并没有确定的归属,换句话说,它只属于其所写的Lua文件,而不是写在文件中的某个Table表。