lua 面向对象编程 lua的面向对象_lua


文章目录

  • 一、面向对象
  • 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表。