Lua 中继承与多态继承的实现

  • 引言
  • 目标
  • 背景知识
  • 类的特性
  • 原表
  • 实现
  • 类 - 封装
  • 继承类 - 继承与多态
  • 继承类 - 多重继承
  • Reference


引言

在 lua 中并没有类的概念。但是 lua 有一个数据结构 table,我们可以基于 table 数据结构来实现类似于 C++ 中的类。

目标

  • 实现一个类似其他语言中的类的 class
  • 支持类与类之间的继承
  • 支持类之间的多重继承

背景知识

类的特性

  • 封装:面向对象编程与面向过程编程最大的区别。将客观事务封装成抽象的类
  • 继承:面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
  • 多态:同一个行为具有多个不同表现形式或形态的能力。是指一个类实例(对象)的相同方法在不同情形有不同表现形式。

原表

Lua 中的原表(metatable) : Lua metatable

实现

类 - 封装

最开始,我们不考虑继承多态等问题,我们首先实现一个类。最直观的特征,类应该包含一些自己的数据与方法。

首先我们实现最初版本的 class 函数。main 中的代码展示了如何调用我们编写的 class 来生成一个类。

-- function class, used for defing a new class
local class = function ( class_name )
	if type(class_name) ~= "string" then
		error("class name must be string")
	end

	local cls = {__class_name = class_name}
	
	-- 默认构造函数 do nothing
	cls.ctor = function ( ... )
		print("default ctor function")
	end

	cls.new = function ( ... )
		local o = {}
		setmetatable(o, {__index = cls})
		o.__class = cls
		o.ctor(...)
		return o
	end

	return cls
end


function main()
	local animal = class("animal")

	-- 重写构造函数,为 self.name 赋值
	function animal:ctor( ... )
		print("constructor an animal ", ...)
		self.name = ...
	end

	function animal:run()
		print(string.format("%s is running.", self.name))
	end

	local animal_1 = animal:new("animal_1")
	animal_1:run()
	local animal_2 = animal:new("animal_2")
	animal_2:run()
end

main()
-- output:
constructor an animal 	animal_1
animal_1 is running.
constructor an animal 	animal_2
animal_2 is running.

NOITCE: 这里没有实现析构函数,各位看官可自行实现。

继承类 - 继承与多态

通过上一步,我们已经有一个类的雏形了。现在我们在上文 class 函数的基础上再做改造,来实现一个支持继承的类,同时它还支持多态。

NOTICE: 此部分基于 metatable 实现,请务必了解 metatable 的用途与用法。见: 原表

-- rewrite function class, support inheritance
local class = function (class_name, base_class)
    if type(class_name) ~= "string" then
        error("class name must be string")
    end

    local cls = {__class_name = class_name}

	-- 新增代码,以上部分代码与上文一致
    if base_class then
        cls.__super = base_class
        -- 将 metatable 的 __index 方法指向父类
        -- 天然地实现了继承
        -- 当数据/方法不存在于子类时,检索父类
        setmetatable(cls, {__index = cls.__super})
    else
        setmetatable(cls, {__index = cls})    
    end
    -- 以下部分与上文一致
    
    cls.ctor = function ( ... )
        print("default ctor function")
    end

    cls.new = function ( ... )
        local o = {}
        setmetatable(o, {__index = cls})
        o.__class = cls
        o.ctor(...)
        return o
    end

    return cls
end
function main()
	-- Base Class
    local animal = class("animal")

    function animal:ctor( ... )
        print("constructor an animal ", ...)
        self.name = ...
    end

    function animal:run( ... )
        print(string.format("%s is running.", self.name))
    end

	-- Child Class
    local turtle = class("turtle", animal)

    function turtle:ctor( ... )
        self.__super:ctor(...)
        print("constructor a turtle ", ...)
        self.name = ...
    end

    function turtle:swim( ... )
        print(string.format("%s is swimming.", self.name))
    end

	-- rewrite run method
    function turtle:run( ... )
        print(string.format("A turtle %s is running.", self.name))
    end

    local turtle_1 = turtle:new("turtle_1")
    local animal_1 = animal:new("animal_1")
    turtle_1:swim()

    local function run_animal(a)
        a:run()
    end
    
    run_animal(turtle_1)
    run_animal(animal_1)
end

main()
-- output
constructor an animal 	turtle_1
constructor a turtle 	turtle_1
constructor an animal 	animal_1
turtle_1 is swimming.
A turtle turtle_1 is running.
animal_1 is running.

注意输出的第五行,不是 turtle_1 is running. 而是 A turtle turtle_1 is running. 这里我们可以发现父类(animal)的 run 方法已经被子类(turtle)的 run 方法覆盖,或称重写。(注意与重载区分)
即,我们实现了多态。

继承类 - 多重继承

在上一部分,我们已经实现了一个可以支持继承与多态的类。其实至此,面向对象的三大特性已经全部实现了。
在本部分,我们修改 metatable__index 的逻辑,使得我们的类支持多重继承。

-- rewrite function class, support multiple inheritance
local class = function (class_name, ...)
    if type(class_name) ~= "string" then
        error("class name must be string")
    end

    local cls = {__class_name = class_name}

	-- 新增代码,以上部分代码与上文一致
    local base_classes = {...}
    cls.__supers = {}
    local cnt = 1

	-- 将传入的基类全部存至 cls.__supers 中
    for _, base_class in ipairs(base_classes) do
        cls.__supers[cnt] = base_class
        cnt = cnt + 1
    end

    if #cls.__supers > 0 then       
        setmetatable(cls, {
            __index = function(_, key)
            	-- 轮训所有父类是否含有该属性/方法
            	-- 有即立刻返回
                for _, super in ipairs(cls.__supers) do
                    if super[key] then
                        return super[key]
                    end
                end
            end
        })
    end
    -- 以下部分与上文一致    
    
    cls.ctor = function ( ... )
        print("default ctor function")
    end

    cls.new = function ( ... )
        local o = {}
        setmetatable(o, {__index = cls})
        o.__class = cls
        o.ctor(...)
        return o
    end

    return cls
end

可见,与继承类 - 继承与多态中的 class 相比,此时的 class 再次修改了 metatable 的 __index 逻辑。当某属性/方法不存在于子类中时,将依次轮训父类中有无该属性/方法。基于此逻辑,实现了多重继承。这样的多重继承,也存在一个问题。就是当不同父类都具有相同属性/方法时,实际的继承结果就与父类的书写顺序有关。

function main()
    -- class boy ################################################################
    local boy = class("boy")

    function boy:ctor( ... )
        print("constructor a boy ", ...)
        self.sexual = "male"
    end

    function boy:say()
        print(string.format("%s is a boy", self.name))
    end
    -- end class boy ############################################################

    -- class student ############################################################
    local student = class("student")

    function student:ctor( ... )
        self.number = 1
        self.student_name = "student_" .. ...       
        print("constructor a student ", self.student_name)
    end

    function student:say()
        print(string.format("%s is a student", self.name))
    end

    function student:study()
        print(string.format("No.%s %s is doing homework", self.number, self.name))
    end
    -- end class student ########################################################

    -- class bad guy ############################################################
    local bad_guy = class("bad_guy", boy, student)

    function bad_guy:ctor( ... )
        for _, super in ipairs(self.__supers) do
            super:ctor(...)
        end
        print("constructor a bad guy ", ...)
        self.name = ...
    end

    function bad_guy:do_bad_thing()
        print(string.format("No.%s %s is doing bad things", self.number, self.name))
    end
    -- end class bad guy ########################################################

    local bg_1 = bad_guy:new("Tom")

    bg_1:say()
    bg_1:study()
    bg_1:do_bad_thing()
end

main()
-- output
constructor a boy 	Tom
constructor a student 	student_Tom
constructor a bad guy 	Tom
Tom is a boy
No.1 Tom is doing homework
No.1 Tom is doing bad things

注意输出的第四行 Tom is a boy ,此时并非输出 Tom is a student ,即多重继承的父类书写次序问题,此时的子类继承了第一个传入的父类 boysay 方法。