An article on understanding Lua's object orientation

Posted by sxiix on Fri, 11 Feb 2022 15:20:12 +0100

Copyright notice: This article is the original article of the blogger and follows CC 4.0 BY-SA Copyright agreement, please attach the original source link and this statement for reprint.
Link to this article: https://blog.csdn.net/takashi77/article/details/116655240

Lua is a process oriented and functional programming language, because Lua is aimed at developing small and medium-sized programs and is often not used to write large programs; Therefore, it does not provide the idea of object-oriented, and many of them are simulated; The key here is meta table and meta method. Through meta table and meta method, we can simulate some behaviors or ideas in object-oriented language



1, Lua's class

A class is the mold to create an object, and an object is an instance of a specific class; In Lua, table can have attributes (member variables) or member methods (implemented through table+function); Therefore, the attributes of an object can be described by table
First, let's talk about the "." when defining functions in Lua The difference between "Lua" and "this" is similar to that of C++ in this. In self, when we define functions, we use colons, which imply that a current caller is passed by implicit.

-- hold tb In table a and b Additive program
local tb = {a=0,b=1}
function tb.add(a,b)
	return a+b
end

print(tb.add(tb.a,tb.b))

It can also be written in another way. You only need to pass a parameter

-- Pass a parameter implementation tb Add two numbers in the table
local tb = {a=0,b=1}
function tb.add(self)
	return self.a+self.b
end

print(tb.add(tb))

When we don't want to pass parameters, we can define and call functions through colons, which is similar to the method of passing tb in the past. But here, we can implicitly pass ourselves in the past by defining functions. Of course, we can also point to use with colons to specify the table passed in the past

local tb = {a=0,b=1}
function tb:add()
	return self.a+self.b
end

print(tb:add())			--A colon is required for the call
--print(tb.add(tb))		--It is also feasible. The colon definition function is actually implicitly passing itself through the first parameter
print(tb.add(tb1))		-- You can also call without colon, and use point to specify the passed table



After basically understanding some operations of table, this is the implementation of a simple class in Lua; The following defines a table named a, then creates an object tb, and then uses tb to call the method MyPrint in the class

local a = {name="lisi"}		--->name belong to a A member variable in the table

function a:new( o )			----Similar to the constructor, it is used to create a class object
	o = o or {}
	setmetatable(o,self)
	self.__index = self

	return o
end

function a:MyPrint(  )		---->new belong to a A member method in a table
	print(self.name)
end


local tb = a:new(nil)		-----Instantiate object
tb:MyPrint()



2, Monadic inheritance of lua


In Lua, the inheritance in the object-oriented idea is realized through table and meta table, mainly through setmetatable and__ The index two fields are implemented, because when we set the meta table for a table using setmetatable, if the variable or function cannot be found in the current table, it will be based on__ Find the place contained in the index field (if _index contains a table, it will traverse whether the variable exists in the contained table. If _index contains a function, it will call the function); This is similar to the relationship between base class and derived class in our object-oriented thought. In object-oriented languages such as C + +, variables or functions that cannot be found in the derived class will be found in the base class.

But compared with object-oriented languages such as C + +; It's not so easy to use; In addition, we should pay attention to the relationship between the base class and the derived class when using it, which sometimes leads to confusion in memory access due to carelessness; In general, different operations as far as possible should be independent as a function, otherwise it may occur that the derived class operates on the variables of the base class, for example, self. In the following program Name = newname is put into the new function, which leads to the initial value of the name of an object after each re creation, and the value of the name in the previously created object cat will also change; The actual operation here has always been the variables in the base class, not its own variables

animal = {name="default"}

function animal:new(tb,newname)
	tb = tb or {}		----->if tb by nil Assign an empty table 
	setmetatable(tb,self)		--->Set as meta table
	self.__index = self
	
	self.name = newname

	return tb
end

function animal:myprint()
	print(self.name)
end

cat = animal:new(nil,"cat")
cat:myprint()				----->Print cat

dog = animal:new(nil,"dog")	----->What is modified here is actually the variable of the base class
dog:myprint()				---->Print dog

cat:myprint()				---->Print dog

In the above program, this inheritance will cause multiple derived classes to access variables in the base class; The reason for this is that when you create a new object, every time the self pointer is a variable in the table "animal"; To solve this situation, we need to redefine a function specially used to initialize the variable name. In this way, each call will pass itself because of the colon. When setting the name, if you don't have the variable name, you will reallocate memory for assignment; In this way, multiple derived classes can share variables in the memory of the base class (if you don't understand this paragraph, you can directly look at the code)

animal = {name="default"}

function animal:new(tb)
	tb = tb or {}		----->if tb by nil Assign an empty table 
	setmetatable(tb,self)		--->Set as meta table
	self.__index = self

	-- self.name = newname		--Here is quite so animal.name = newname

	return tb
end

function animal:myprint()
	print(self.name)
end


function animal:setname( name )
	self.name = name
end

cat = animal:new(nil)
cat:setname("cat")	---here cat call setname After the function, the function is equivalent to cat.name = name
cat:myprint()

dog = animal:new(nil)
dog:setname("dog")	---here dog call setname After the function, the function is equivalent to dog.name = name
dog:myprint()

mouse = animal:new(nil)
mouse:myprint()

dog:myprint()		---Print again at this time dog of name It won't appear because of the previous modification

In this program, the variables are listed separately; Each operation needs to call the function. When the function is called, the current self becomes the object that is currently invoked. In this way, if the variable name does not exist during the operation, one will be created in the current object and then assigned; If the variable exists, it will be operated directly


3, Multiple inheritance of lua


In the single inheritance of lua, setmetatable and__ The index field contains a table; What is similar to but different from single inheritance in multi inheritance is that in multi inheritance__ When the index field contains a function; When a meta table is set for a table, if the value of the key cannot be found in the original table during execution, this function will be called to find the key elsewhere; Therefore, we can use this feature to realize multi inheritance in Lua

First, let's take a look at how to find a field in multiple table tables

function seach( tb,key)
	for i=1,#tb do
		if tb[i][key] then		--As long as the field you are looking for key The value of is not nil Just return
			return tb[i][key]
		end
	end
end

local t1 = {name="lisi"}
local t2 = {age=18}

print(seach({t1,t2},"age"))	--hold t1,t2 Put them into a table, which is equivalent to a table set table, and then traverse the fields in each table

Here is a brief description of the implementation process:

1. First, use a table to receive t1 and t2 tables, and then pass them together with the key to be found to the search function through call
2. Then start from the first table and traverse one by one until you reach the end of the table
3. If there is a key in the ith table and it is not nil, the value of the key will be returned (false if the key is nil, it will not be returned)


Since the search function is the key in multi inheritance, after knowing how to find a key in multiple tables, you can start to enter the topic: how Lua implements multi inheritance. Firstly, because multiple inheritance means that a derived class has multiple base classes, we cannot create it through the methods in a class. At this time, we need to define a special function to create a new derived class. The following AnimalClass function is used to implement a new derived class

function search( tb,key )		--Traverse whether the method or variable exists in multiple base classes, and it does not exist nil Just return
	for i=1,#tb do
		if tb[i][key] then
			return tb[i][key]
		end
	end
end


function AnimalClass( ... )
	local TbColletion = {...}		--Used to receive multiple tables passed in
	local o = {}					--This is every time new Create an empty table

	setmetatable(o,{__index = function (table,key )		--Set the meta table. If the current table does not exist, this function will be called every time a field is found, and then called again search Function traverses multiple base classes
		return search(TbColletion,key)
	end}) 

	function o:new(  )				--Returned for o Write a table new Method, if the derived class is not overridden new Method will call this method new
		NewTable = {}
		setmetatable(NewTable,self)
		self.__index = self			
		return NewTable
	end

	return o
end


CatTb = {name="Garfield"}			
function CatTb:NamePrint()
	print(self.name)
end

function CatTb:new( )
	o = {}
	setmetatable(o,self)
	self.__index = self		

	return o
end


DogTb = {behavior="having dinner"}

function DogTb:BehPrint( )
	print(self.behavior)
end

function DogTb:new(  )
	o = {}
	setmetatable(o,self)
	self.__index = self

	return o
end

-------create object-----------------
local MulTb = AnimalClass(CatTb,DogTb)
local MyTb  = MulTb:new()

MyTb:NamePrint()
MyTb:BehPrint()

Execution result:

The execution process of the program can be divided into:

1. Call the AnimalClass function and pass two tables to create the object. The new object will inherit from these two tables
2. Set the meta table for the newly created object and set__ The index field is a function, which is mainly used to call the search function
3. Returns the newly created table o
4. Call the new function with the newly created returned object, because if the returned object has no new function, it will be called according to the__ Functions contained in index
5. Then call the search function to find the new function; The search function is responsible for traversing all the tables passed when creating the object (tbcollision is used to accept all the tables passed when saving the created object)
6. Other functions will be called according to this law

Note that in multi inheritance, each class created and instantiated is single and can call the functions or variables of all base classes inherited in this class; However, due to the complexity of search function, the performance of multi inheritance is not as good as that of single inheritance


4, Lua's privacy


The essence of lua's privacy implementation idea is to represent an object through two tables. One of the two tables uses and to represent the state (which stores variables and functions that are not accessed externally), and the other is the operation of saving the object (that is, it is equivalent to leaving variables or functions accessible to the object); When we use the AnimalClass in the following program to create an object, the object actually returns a table with the specified function interface, and if we want to access or modify the data of the self table, we can only access it through the interface in the table we return; Therefore, we call the unreturned function cat at the end of the program An error will be reported when thinking (), indicating that think is a nil value. Because there is no such function in the returned table, a think will be created when accessing, but there is nothing in this think, so it is a nil at this time

function AnimalClass(  )
	local self = {name="default",behavior="default"}		---->A table that holds the state of an object variable

	function SetBehavior( NewBehavior )
		self.behavior = NewBehavior
	end
	function BehaviorPrint( )
		print(self.behavior)
	end

	function SetName( NewName )
		self.name = NewName
	end
	function NamePrint(  )
		print(self.name)
	end


	function think(  )
		print("i am thinking...")
	end

	return {
		SetBehavior=SetBehavior,		----->Use a table to return the interface that can be accessed externally. The previous key Can be customized; But for the convenience of identification, the general names are consistent
		BehaviorPrint=BehaviorPrint,
		SetName=SetName,
		NamePrint=NamePrint
	}
end


cat = AnimalClass()
cat.SetBehavior("having dinner")
cat.BehaviorPrint()

cat.SetName("Garfield")
cat.NamePrint()

 -- cat.think()		---->An error will be reported here because the function does not return an interface 
 

You can also use the following method to realize object-oriented privacy. Compared with the above code, you can clearly see the relationship between the two tables. obj table is used to store object state, or variables or data that you don't want to be accessed externally. Then, the option table is used to return the function interface provided to the external call

function createClass( InitName , InitId )
	local obj = {name =InitName,id=InitId }
	local option = {}

	function option.SetInfo( NewName,NewId )
		if NewName==nil or NewId==nil then
			if NewId~=nil then
				obj.id = NewId 
				print("Successfully modified ID")
				-- return "Successfully modified ID"
			elseif NewName~=nil then
				obj.name = NewName
				print("Name successfully modified")
				-- return "Name successfully modified"
			else
				error("Failed to assign successfully")				----If both parameters are nil If you want to error An error is reported, and the subsequent program is not running
				-- return "Failed to assign successfully"
			end
		end

		obj.name = NewName
		obj.id 	 = NewId
		return "Successfully modified name ID"
	end
	local function MyPrint(  )
		print(obj.name,"====>",obj.id)
	end

	function option.InfoPrint(  )
		MyPrint()
	end

	
	return option
end


local obj1 = createClass("lisi",21)
obj1.InfoPrint()
obj1.SetInfo("qiqi",nil)
obj1.InfoPrint()
print(obj1.name)		------>Print nil,External access is not available, here name amount to option In table name

-- obj1.MyPrint()		----->An error will be reported here because the function is not in the returned table



5, Single method approach

When there is a situation in the privacy of object-oriented programming above: if there is only one method for an object at present, you can directly return the function without creating a table that returns the external access interface; Although this situation can not guarantee inheritance, it also has complete privacy control

function AnimalClass( value )

	return function ( action,v )		---->Return the function directly
		if action == "get" then return value 
		elseif action == "set" then value = v
		else error("invaild action")
		end
	end
end

d = AnimalClass(0)
print(d("get"))
d("set",10)
print(d("get"))
--print(d.value)		---->Will report an error



6, Implementation of polymorphism


In Lua, function overloading cannot be carried out directly like C + +, because the previous function will be overwritten by the last function with the same name anyway; Similarly, the same name function of the derived class also overrides the function of the same name in the base class (so the function of the derived class is invoked when the function of the same name in the base class is overloaded in the derived class), but the function of the base class can be invoked by the specified name. If we want to achieve heavy loading, we can only write a function, although there is no such flexible syntax in Lua. However, we can accept multiple parameters through a function, then judge whether the corresponding parameter is nil, and then carry out the corresponding operation; Similar to the tb:GetAreatb:GetArea function in the following program

tb = {area = 0}

 function tb:new( o )
	o = o or {}
	setmetatable(o,tb)
	self.__index = self

 function tb:GetArea(side,len )
	if len==nil or side==nil then	--as long as side or len There is a nil Just enter
		if len~=nil then			--If len Not for nil namely side by nil,incorrect side Operate
			self.area = len*len
		elseif side~=nil then		--If side Not for nil namely len by nil,incorrect len Operate
			self.area = side*side
		else
			self.area = 0
		end
		return 
	end
	self.area = len*side
end

function tb:PrintArea(  )
	print(self.area)
end

---------create object----------
NewTb = tb:new(nil)
NewTb:GetArea(3,5)
NewTb:PrintArea()

-------Derived class overloads base class function-------
function NewTb:GetArea( len )
	self.area = len*len
end

NewTb:GetArea(5)	--Functions in derived classes are called by default after overloading
NewTb:PrintArea()	--This function is not overloaded, and the function called is still the function of the base class


--Call the base class function with the specified name
tb1:GetArea(5,3)
tb1:PrintArea()




The article may not be well written. If there are mistakes, please inform me by private letter; Learn and grow together!
No reprint without consent

Topics: Programming lua