【Lua学习笔记】Lua进阶——Table(4)继承,封装,多态

news2024/9/28 9:24:08

在这里插入图片描述

文章目录

  • 封装
  • 继承
  • 多态


封装

// 定义基类
Object = {}

//由于表的特性,该句就相当于定义基类变量
Object.id =1

//该句相当于定义方法,Object可以视为定义的对象,Test可以视为方法名
//我们知道Object是一个表,但是抽象地看,请把Object看着面向对象中的 “对象”
function Object:Test()
    print(self.id)
end
// 以上语句等同于:
// public class Object{
	int id=1;
	void Test(Object obj)
		print(obj.id);
}


//定义一个new方法,用于创建这个基类的对象
function Object:new()
	//定义空表obj,用面向对象比喻相当于new了一个空对象
    local obj = {}
    //绑定元表,将元表看作一个基类
    self.__index = self
    setmetatable(obj, self)
    //返回空对象
    return obj
end

local Car = Object:new()  //实际是将new出的空对象return给外部定义的Car
// 以上语句等同于:
// Object Car = new Object();

// 由于Car实际上是空的table,所以访问Car其实是通过__index访问基类中的索引
// 相当于虽然没有定义Car内的变量,但初始化时继承基类的值作为了初始值
print(Car.id) --1,来自元表

// 同样的,Car实际使用了__index基类提供的方法
// 但是由于入参是self,此处就是Car,print(Car.id),最终还是访问了基类__index找到的Object.id
Car:Test() --1,来自元表

// 定义Car中的变量
Car.id = 2
// 现在Car表中有了索引id,那么就能找到这个索引,所以输出为2
Car:Test() --2,来自子表

现在我们可以像面向对象一样,new一个对应基类的对象了。但是这里的new也不完全相似与面向对象的new,例如我们可以这样做:

Car.name = "a"
print(Car.name)
输出:
a

我们在封装Object类的时候可完全没有name这个索引,而在Lua中我们new了一个新对象,还能新加入一些变量和方法,这些特性明显是继承了父类的子类才有的。算不上坏处,不过我们想要完全实现封装还能加以限制:

//定义一个垃圾列表,将添加到子类的垃圾都丢进去
garbage={}

//定义一个new方法,用于创建这个基类的对象
function Object:new()
	//定义空表obj,用面向对象比喻相当于new了一个空对象
    local obj = {}
    // 禁止子类的添加
    self.__newindex = garbage
    //绑定元表,将元表看作一个基类
    self.__index = self
    setmetatable(obj, self)
    //返回空对象
    return obj
end
local Car = Object:new()
Car.name = "a"
print(Car.name)

输出:
nil

现在我们确实实现封装了,既能访问基类的方法和变量,又能阻止新加的其他东西,但是还得把垃圾及时清理,这点我们将在后文垃圾回收中讲解。


继承

面向对象重要的特性之继承,光new一个新对象无法满足全部需要,我们想要重写父类的一些方法而非直接使用它们,就需要继承。

观察上面的Object:new()代码,其实我们如果想用进行继承,其实只需要在上面改改即可

Object = {}
Object.id = 1;
function Object:Test()
    print(self.id)
end

//换种方式,如果我们不return的话,想要返回这个值,可以直接把它丢进全局表中
function Object:subClass(className)
    _G[className] = {}
    self.__index = self
    setmetatable(_G[className], self)
end
Object:subClass("Cat")
print(Cat.id)
输出:
1

继承比封装还要简单一点,其实它和我们第一次定义的封装是一模一样的,只是换了种方式来实现。

// new一个Cat类的对象
local WhiteCat = Cat:new()
print(WhiteCat.id) -- 1
function Object:Test()
    print("我是基类")
end

function Object:new()
    local obj = {}
    self.__newindex = garbage
    self.__index = self
    setmetatable(obj, self)
    return obj
end

function Object:subClass(className)
    _G[className] = {}
    self.__index = self
    setmetatable(_G[className], self)
end

//Cat继承基类
Object:subClass("Cat")
//new一个Cat类的对象WhiteCat
local WhiteCat = Cat:new()
WhiteCat:Test()  -- 我是基类

// 重写Test方法(其实只是新写了一个放在Cat表里被调用,更像重载?)
function Cat:Test()
    print("我是猫类")
end
WhiteCat:Test()  --我是猫类

//想要重写Cat的Test方法?不好意思我已经用__newindex封装好了
//白猫是个对象,而不是Cat这个类,它不应该重写方法
//下面重写的方法会被丢到garbage里
function WhiteCat:Test()
    print("我是白猫")
end
WhiteCat:Test() --我是猫类
garbage:Test() --我是白猫

如果看不明白,建议重学Table,元表以及面向对象


多态

多态就是对于一个父类的相同方法,子类可以执行不同的逻辑。实现多态我们可以怎么做?

  1. 重写和重载方法
  2. 实现接口
  3. 实现抽象类和抽象方法

如果重写应当是这样:

function Object:Test()
    print("我是基类")
end
Object:subClass("Cat")
Object:subClass("Dog")
function Cat:Test()
    print("我是猫类")
end
function Dog:Test()
    print("我是狗?")
end

重写固然可以实现,问题在于继承了父类之后的重写是无法保留父类的同名方法的,那我想要访问父类的方法怎么办?

别忘了我们的类其实是个table,我直接把父类存进去,然后要使用的时候访问不就行了吗?反正又没有面向对象语法限制。

function Object:subClass(className)
    _G[className] = {}
    local obj = _G[className]
    self.__index = self
    // 直接把父类表存进子类的base
    obj.base = self
    setmetatable(obj , self)
end

function Dog:Test()
    print("我是狗?")
end
Dog:Test()
Dog.base:Test()

输出:
我是狗?
我是基类

function Dog:Test()
	// 如果想在继承了父类的方法的基础之上重写
    self.base:Test()
    print("我是狗?")
end

Dog:Test() --我是基类 我是狗?

注意,如果我们直接用的父类方法,在调用父类的时候应当避免不同的类共享全局变量:

Object = {}
Object.id = 1;
function Object:Test()
    self.id = self.id + 1
    print(self.id)
end

Object:subClass("Cat")
function Cat:Test()
	self.base:Test()
    print("我是猫类")
end
Object:subClass("Dog")
function Dog:Test()
    self.base:Test()
    print("我是狗?")
end

输出:
2
我是猫类
3
我是狗?

原因也很简单,table内存放了父类的table,我们直接调用父类的Test方法,那么self.id每次调用都会加一。两个table中的父类是同一个地址,而Object:Test()这个方法中每次传入给self的都是这个xxx.base,也就是这个父类table本身,所以self.id增加的是父类中的id,作为一个全局变量,它自然是不断增加的。

那么我们想让子类既能在继承Object:Test()这个父类方法基础之上重写,又想使得self.id改变的self是我们使用方法的那个子类table,那么就应当这样写:

Object = {}
Object.id = 1;
function Object:Test()
    self.id = self.id + 1
    print(self.id)
end

Object:subClass("Cat")
function Cat:Test()
	// 手动地传入参数,因为冒号传入给self的是base
	// 因此需要手动地改变传入的参数的值
	self.base.Test(self)
    print("我是猫类")
end
Cat:Test()

输出:
2
我是猫类

重载应该是最简单的多态方法,只需要改变函数的入参数量就行了

至于接口和抽象类,lua本身的函数就可以重写,抽象性还是很强的。而接口我们应当可以访问另一个table结构来实现,例如self.base应当就能视为一种接口,当然这些只是我的想法,目前还没学习到。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/804860.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么要有虚拟内存?

操作系统是通过内存分段和内存分页的方式管理虚拟内存地址和物理内存地址之间的关系 内存分段 程序是由若干个逻辑分段组成的,代码分段、数据分段、栈段、堆段组成,不同的段有不同的属性,所以就用分段的形式分离开。 分段机制下的虚拟内存…

【业务功能篇58】Springboot + Spring Security 权限管理 【下篇】

4.2.2.3 SpringSecurity工作流程分析 SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。这里我们可以看看入门案例中的过滤器。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KjoRRost-1690534711077)(http…

使用Django自带的后台管理系统进行数据库管理的实例

Django自带的后台管理系统主要用来对数据库进行操作和管理。它是Django框架的一个强大功能,可以让你快速创建一个管理界面,用于管理你的应用程序的数据模型。 使用Django后台管理系统,你可以轻松地进行以下操作: 数据库管理&…

详解机器学习中的熵、条件熵、相对熵和交叉熵

这个是讲的不错的链接 https://www.cnblogs.com/kyrieng/p/8694705.html 这个是交叉熵 https://blog.csdn.net/m0_57236802/article/details/129554878

《焊接点云处理》-角焊焊缝处理

角焊缝点云处理 前言一、代码二、实现步骤3、验证前言 针对T型板,识别效果如下所示 一、代码 主函数 #include "CGALRECONSTRUCT.h" #include "CGALREGIONPLANE.h" #include

设计利器,掌握CAD辅助命令的必备指南

CAD设计中的辅助命令是提高效率和确度的关键工具。掌握并正确运用CAD中的各种辅助命令对于设计师们来说至关重要。本文将为你详细介绍如何使用CAD中的辅助命令,从而帮助你在设计过程中更加高效地实现你的创意。、 大家有没有发现,当我们的直线命令移动到…

Rethinking the Image Fusion(PMGI)

1.摘要 本文提出了一种基于梯度和强度比例维护(PMGI)的快速统一图像融合网络,可以端到端实现各种图像融合任务,包括红外和可见图像融合、多曝光图像融合、医学图像融合、多焦点图像融合和全色增强。我们将图像融合问题统一为源图…

C++信号量与共享内存实现进程间通信

关于信号量和共享内存的相关知识可参考下面链接: 进程间通信方式介绍_夜雨听萧瑟的博客-CSDN博客 C 创建共享内存_c共享内存_夜雨听萧瑟的博客-CSDN博客 信号量SytemV与Posix信号量的介绍与用法_夜雨听萧瑟的博客-CSDN博客 直接上代码,代码如下&#…

蓝桥杯单片机第十二届国赛 真题+代码

iic.c /* # I2C代码片段说明1. 本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 参赛选手可以自行编写相关代码或以该代码为基础&#xff0c;根据所选单片机类型、运行速度和试题中对单片机时钟频率的要求&#xff0c;进行代码调试和修改。 */ #include <STC1…

golang文件锁,目录锁,syscall包的使用

先说结论 1. golang提供了syscall包来实现文件/目录的加锁&#xff0c;解锁 2. syscall包属于文件锁&#xff0c;是比较底层的技术&#xff0c;并不能在所有操作系统上完全实现&#xff0c;linux上实现了&#xff0c;windows下面就没有 3. 加锁时调用syscall.Flock(fd&#…

安全学习DAY09_加密逆向,特征识别

算法逆向&加密算法分类&#xff0c;特征识别 文章目录 算法逆向&加密算法分类&#xff0c;特征识别算法概念&#xff0c;分类单向散列加密 - MD5对称加密 - AES非对称加密 - RSA 常见加密算法识别特征&#xff0c;解密特点MD5密文特点BASE64编码特点AES、DES特点RSA密文…

leaftjs实现全国温度降水气压风速等值面风场洋流效果

实现内容 数据爬取、地图marker聚合、鼠标移动显示pop&#xff0c;风场&#xff0c;洋流&#xff0c;温度等值面、降水等值面、气压等值面、风速等值面&#xff0c;洋流方向、洋流流速展示、风场方向、风场风速展示&#xff0c;后期扩展小时预报&#xff0c;分钟预报、7天预报…

Modbus RTU协议 + 调试工具 + java工具类

春风若有怜花意&#xff0c;可否容我再少年 Modbus RTU通信协议指令学习 Modbus RTU协议是一种紧凑的&#xff0c;采用二进制表示数据的方式&#xff0c;带有循环冗余校验的校验和。 读取指令格式 使用过程中03功能码比较常用&#xff0c;所以以03读取为例 读请求&#xff…

Java的代理模式

java有三种代理模式 静态代理 jdk动态代理 cglib实现动态代理 代理模式的定义&#xff1a; 为其他对象提供一种代理以控制对这个对象的访问。在某些情况下&#xff0c;一个对象不适合或者不能直接引用另一个对象&#xff0c;而代理对象可以在客户端和目标对象之间起到中介的…

打包出现ProjectBuildingException异常原因之一

一次正常打包操作突然出现 原因是同事不小心在集合模块里面添加了重复的模块引入

Github 上 爆火,标星 103K的 Spring Security 手册及源码笔记,YYDS

Spring Security 是一个基于 Spring AOP 和 Servlet 过滤器的安全框架&#xff0c;它提供了安全性方面的解决方案 Spring Security 作为非常强大的框架&#xff0c;作为程序员是非常热爱的&#xff0c;我这里整理了四份 Spring Security 手写笔记及实战手册原文档见文末 目录…

FANUC机器人实现2个RO输出信号互锁关联(互补)的具体方法

FANUC机器人实现2个RO输出信号互锁关联(互补)的具体方法 一般情况下,为了方便用户控制工装夹具上的电磁阀等控制工具,FANUC机器人出厂时给我们提供了8个RO输出信号,如下图所示,这8个RO信号可以各自单独使用。 那么,如果为了安全控制,需要将2个RO信号成对的进行安全互锁…

【C语言进阶篇】回调函数都学了吧!那么用冒泡排序实现qsort函数你会嘛?

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言&#x1f4ac; qsort 和 冒泡排序的区别&#x1f4d1; qsort 的特点&#x1f4d1; 冒泡排序 …

干货 | 常见电路板GND与外壳GND之间接一个电阻一个电容,为什么?

干货 | 常见电路板GND与外壳GND之间接一个电阻一个电容&#xff0c;为什么&#xff1f; 外壳是金属的&#xff0c;中间是一个螺丝孔&#xff0c;也就是跟大地连接起来了。这里通过一个1M的电阻跟一个0.1uF的电容并联&#xff0c;跟电路板的地连接在一起&#xff0c;这样有什么好…