【Lua 入门基础篇(十三)】面向对象

简介: 笔记

一、面向对象


面向对象编程(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) 访问属性

我们可以使用点号(.)来访问类的属性:


r:printArea()


(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表。

相关文章
|
7月前
|
C++ 索引 Python
Lua中self 、自索引及其面向对象应用代码示例
Lua中self 、自索引及其面向对象应用代码示例
|
2月前
lua面向对象(类)和lua协同线程与协同函数、Lua文件I/O
Lua的面向对象编程、协同线程与协同函数的概念和使用,以及Lua文件I/O操作的基本方法。
37 4
lua面向对象(类)和lua协同线程与协同函数、Lua文件I/O
|
NoSQL 安全 Java
Redis从入门到精通之Lua 脚本
Lua 是一种轻量级的脚本语言,被广泛应用于游戏开发、嵌入式系统、Web 开发、科学计算等领域。Redis 内置了 Lua 解释器,使得用户可以通过编写 Lua 脚本来扩展 Redis 的功能。在 Redis 中,可以使用 EVAL 和 EVALSHA 命令执行 Lua 脚本。
808 11
Redis从入门到精通之Lua 脚本