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

在这里插入图片描述

文章目录

  • 封装
  • 继承
  • 多态


封装

// 定义基类
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应当就能视为一种接口,当然这些只是我的想法,目前还没学习到。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Jenkins构建完成后发送消息至钉钉

钉钉群的最终效果: 1、jenkins安装DingTalk插件,安装完成后重启 2、配置钉钉插件 参考官网文档:快速开始 | 钉钉机器人插件 系统管理 拉到最下面,可以看到钉钉配置 按照如下配置钉钉机器人 配置完成可以点击测试按钮&#xff0…

Qt/C++音视频开发50-不同ffmpeg版本之间的差异处理

一、前言 ffmpeg的版本众多,从2010年开始计算的项目的话,基本上还在使用的有ffmpeg2/3/4/5/6,最近几年版本彪的比较厉害,直接4/5/6,大版本之间接口有一些变化,特别是一些废弃接口被彻底删除了,…

Ansible的应用

Ansible简介 Ansible是一个基于Python开发的配置管理和应用部署工具,现在也在自动化管理领域大放异彩。它融合了众多老牌运维工具的优点,Pubbet和Saltstack能实现的功能,Ansible基本上都可以实现。 Ansible能批量配置、部署、管理上千台主机…

Redis(主从复制、哨兵模式、集群)概述及部署

文章目录 一、Redis模式二、Redis 持久化1.Redis 提供两种方式进行持久化:2.RDB 持久化2.1 触发条件2.2 执行流程2.3 启动时加载 3.AOF持久化3.1 执行流程3.1.1 命令追加(append)3.1.2 文件写入(write)和文件同步(sync)3.1.3 文件重写(rewrite) 3.2 文件重写的触发&…

并发编程——线程池

1.概述 如果并发的线程过多,而且执行的时间都非常短,如果这样,每次都要创建线程就会大大降低效率,我们可以通过线程池来解决,JDK5增加了内置线程池ThreadPollExecutor。 2.线程池的优点 1.重复利用,降低…

复现YOLOv5改进最新MPDIoU:有效和准确的边界盒回归的损失,打败G/E/CIoU,效果明显!!!

MPDIoU: A Loss for Efficient and Accurate Bounding Box Regression 论文简介MPDIoU核心设计思路论文方法实验部分加入YOLOv5代码论文地址:https://arxiv.org/pdf/2307.07662.pdf 论文简介 边界盒回归(Bounding box regression, BBR)广泛应用于目标检测和实例分割,是目标…

软件测试员,面试常见的18个问题(记得收藏!)

软件测试面试18个常见问题汇总 Q1:项目中相关需求问题,测试可以直接和客户沟通吗? A1:可以,最初与客户沟通需求时,测试人员直接参与,所以我们可以直接和客户方的代表开会进行沟通。 A2&#xff…

HTTP——二、简单的HTTP协议

本章将针对 HTTP 协议结构进行讲解,主要使用HTTP/1.1版本。学完这章,想必大家就能理解 HTTP 协议的基础了。 HTTP 一、HTTP协议用于客户端和服务器之间的通信二、通过请求和响应的交换达成通信三、HTTP是不保存状态的协议四、请求URI定位资源五、告知服…

【云原生】一文学会Docker存储所有特性

目录 1.Volumes 1.Volumes使用场景 2.持久将资源存放 3. 只读挂载 2.Bind mount Bind mounts使用场景 3.tmpfs mounts使用场景 4.Bind mounts和Volumes行为上的差异 5.docker file将存储内置到镜像中 6.volumes管理 1.查看存储卷 2.删除存储卷 3.查看存储卷的详细信息…

“程序员求职攻略:IT技术岗面试的必备技巧“

文章目录 每日一句正能量前言分享面试IT公司的小技巧IT技术面试有哪些常见的问题?分享总结遇到过的面试题后记 每日一句正能量 人活一世,不在乎朋友多少,不问财富几车,关键看在你最困难的时候,是否有一个伸出援手的人&…

按键消抖(有/无状态机)

一,理论概念 按键抖动 按键抖动:按键抖动通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭…

哥大Salesforce重磅发布!最丰富的统一对话数据集,几乎支持所有对话任务

夕小瑶科技说 原创 作者 | 小戏、Python 尽管以 ChatGPT 为代表的对话式人工智能概念炒的火热,但是事实上作为当下智能发动机的大模型,其真正的动力源泉——数据集——仍然面临诸多困难。 所谓 Garbage In, Garbage Out,这条数据科学的朴素…

Linux复习——基础知识

作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​ 1. 有关早期linux系统中 sysvin的init的7个级别描述正确的是( )[选择1项] A. init 1 关机状态 B. init 2 字符界面多用户模式 …

要单片机和RTOS有必要学习嵌入式linux吗?

学习嵌入式 Linux 是否有必要,取决于你的项目需求和职业发展目标。以下是一些考虑因素: 项目需求:如果你的项目需要处理复杂的网络、文件系统、多任务管理等功能,嵌入式 Linux 可能是更适合的选择。Linux 提供了丰富的开源软件包和…

排序算法汇总

每日一句:你的日积月累终会成为别人的望尘莫及 目录 常数时间的操作 选择排列 冒泡排列 【异或运算】 面试题: 1)在一个整形数组中,已知只有一种数出现了奇数次,其他的所有数都出现了偶数次,怎么找到…

为何押注AI大模型的微软云,业绩增速反而不如谷歌云?

科技云报道原创。 上周微软、谷歌、Meta等国外科技公司相继发布最新财报。作为与人工智能、云计算和数字广告等领域相关的巨头,它们的一举一动都将对市场产生影响,同时也吸引着众多从业者的关注。 在国外三大云巨头中,谷歌云的市场份额长期…

渗透测试:Linux提权精讲(二)之sudo方法第二期

目录 写在开头 sudo expect sudo fail2ban sudo find sudo flock sudo ftp sudo gcc sudo gdb sudo git sudo gzip/gunzip sudo iftop sudo hping3 sudo java 总结与思考 写在开头 本文在上一篇博客的基础上继续讲解渗透测试的sudo提权方法。相关内容的介绍与背…

docker 部署 mysql8.0 无法访问

文章目录 🗽先来说我的是什么情况🪁问题描述🪁解决方法:✔️1 重启iptables✔️2 重启docker 🪁其他有可能连不上的原因✔️1 客户端不支持caching_sha2_password的加密方式✔️2 my.conf 配置只有本机可以访问 &#…

CTF:信息泄露.(CTFHub靶场环境)

CTF:信息泄露.(CTFHub靶场环境) “ 信息泄露 ” 是指网站无意间向用户泄露敏感信息,泄露了有关于其他用户的数据,例如:另一个用户名的财务信息,敏感的商业 或 商业数据 ,还有一些有…

读取application-dev.properties的中文乱码【bug】

读取application-dev.properties的中文编码【bug】 2023-7-30 22:37:46 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是https://blog.csdn.net/qq_51625007 禁止其他平台发布时删除以上此话 bug 读取application-dev.propert…