简单实现Lua的面向对象

参考了多篇文章和以往的项目经验,这里来写一篇在Unity游戏客户端开发面试的时候经常会被问到的如何实现lua的面向对象功能。

众所周知面向对象有三大特性,封装、继承、多态,那么在这里我们用lua来分别实现这三个功能。

一、封装

假设我有一个类,我想要快速创建一个该类的实例化对象,那么我肯定是希望有一个通用的函数来创建,例如new之类的;另外由于lua底层是没有面向对象这种东西的,那么我肯定不希望每次要用一次对象就重新写一套。那么需求就很明确了:1、封装一个基类对象,如Object,此后实现别的对象都可以从此对象中继承(后续完成继承功能);2、完成一系列的基本的对象相关的函数,如new——用于实例化,init——用于初始化,inherit——用于继承,super——用于访问父类等等。

此处假设读者们都有lua基础,其lua基础函数功能可以自行查找其他资料了解(主要是本人也讲不清楚)。在上述的需求下,我们首先设置一个基础的表,叫做Object:

Object = {}
Object.__className = "Object"
function Object:New(...)
	local o = {}
	setmetatable(o, {__index = self})
	o:__init__(...)
	return o
end

function Object:__init__()
	print("this is "..self.__className.." init")
end

function Object:Test()
	print("this is "..self.__className.." Test")
end

local objBase = Object:New()
objBase:Test()

在这里我们的New函数创建了一个空表o,并将其__index与Object链接起来,设置为o的元表,这样o就可以通过键名去访问Object中的对应元素,这里就实现了基本的对象的封装,通过New可以快速创建Object的实例化对象。

二、继承

在实现继承的时候分析如下需求:1、子类需要继承父类的全部变量;2、子类可自由添加新的变量;3、子类需要可以访问父类;4、父类调用子类(子类重写父类同名函数)函数/变量;

当子类需要继承父类中全部变量的时候,可以有诸如

setmetatable(o, {__index = Base})

这样的写法,但是总而言之不够简洁,因为在创建子类的时候我需要在子类中声明

objectChild = {}
function objectChild:New()
    setmetatable(objectChild, {__index = base})--base为父类
end

每次创建子类继承对象的时候就要设置一次元表。那么有什么方法可以更加快速地创建继承的对象呢?答案是在基类中设置一个继承函数Inherit,通过这个函数将父类中的元素都复制到子类的表中,包括new方法:

function Object:Inherit(o)
	local o = o or {}
	
	for k, v in pairs(self) do
		assert(not o[k])
		if not o[k] then
			o[k] = v
		end
	end

	o.__base = self
	return o
end

其中设置base为自身,也为子类提供了访问父类的方法。当我创建一个子类的时候,只需要:

ObjChild = Object:Inherit()
ObjChild.__className = "ObjChild"

function ObjChild:__init__()
	--Super(ObjChild):__init__(self)
	print("this is "..self.__className.." init")
end
function ObjChild:Test()
	print("this is "..self.__className.." Test")
end

调用一次父类中的Inherit函数即可,他会将父类表中所有元素复制到子表中。那么我在实例化子类对象的时候,会调用父类中的New函数,New函数又将创建的实例对象的__index链接到类中来,从而访问到子类中(包括父类)的所有变量。

local objChild = ObjChild:New()
objChild:Test()

        多层继承

关于多层继承,由于上述的Inherit为逐级复制,例如child1继承Object,child2继承child1,那么在child2调用child1:Inherit()方法的时候,实际上child1也一定是由Object:Inherit()得来的,因此child2也会完全的继承Object中的全部元素。

ObjChild2 = ObjChild:Inherit()
ObjChild2.__className = "ObjChild2"

function ObjChild2:__init__()
	Super(ObjChild2):__init__(self)
	print("this is "..self.__className.." init")
end
function ObjChild2:Test()
	print("this is "..self.__className.." Test")
end

local objChild2 = ObjChild2:New()
objChild2:Test()

三、多态

在考虑多态的功能的时候,首先考虑到子类对父类同名函数的重写,这时候重写需要考虑是否调用父类的同名函数。在这里我们首先实现一下访问父类函数的 通用方法:

function Super(obj)
	return obj.__base
end

这里的__base是我们在New的时候将其指向父类的一个变量。这时候可能有的同学可能会问:欸,那我直接通过__base:xxxx()访问不就可以了吗,为什么要多此一举?首先在lua中没有访问权限控制的功能,因此我们在做代码规范的时候会规定诸如下划线开头的变量和方法被认为是私有变量,不能随意访问;大写开头的变量和方法被认为是公有变量,可以随意访问修改等等。

在上述的代码中,会发现我在每个继承的子类中重写了Test函数,在访问实例化的子类对象的时候,调用其Test函数,有的设置了访问父类同名函数,有的没设置,这样就能达到多态的目的。

完整代码如下:

Object = {}
Object.__className = "Object"
function Object:New(...)
	local o = {}
	setmetatable(o, {__index = self})
	o:__init__(...)
	return o
end
function Object:Inherit(o)
	local o = o or {}
	
	for k, v in pairs(self) do
		assert(not o[k])
		if not o[k] then
			o[k] = v
		end
	end

	o.__base = self
	return o
end

function Object:__init__()
	print("this is "..self.__className.." init")
end

function Object:Test()
	print("this is "..self.__className.." Test")
end

function Super(obj)
	return obj.__base
end

---------------------------------------------------------------------

local objBase = Object:New()
objBase:Test()

ObjChild = Object:Inherit()
ObjChild.__className = "ObjChild"

function ObjChild:__init__()
	--Super(ObjChild):__init__(self)
	print("this is "..self.__className.." init")
end
function ObjChild:Test()
	print("this is "..self.__className.." Test")
end

local objChild = ObjChild:New()
objChild:Test()

ObjChild2 = ObjChild:Inherit()
ObjChild2.__className = "ObjChild2"

function ObjChild2:__init__()
	Super(ObjChild2):__init__(self)
	print("this is "..self.__className.." init")
end
function ObjChild2:Test()
	print("this is "..self.__className.." Test")
end

local objChild2 = ObjChild2:New()
objChild2:Test()

运行结果如下:

this is Object init
this is Object Test
this is ObjChild init
this is ObjChild Test
this is ObjChild init
this is ObjChild2 init
this is ObjChild2 Test

这样我们就简单地实现了lua的面向对象功能了。有其他的问题和补充欢迎在评论区提出。

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值