lua实现类

lua实现类

文章目录

  • lua实现类
  • 前言
  • 一、实现一个类
    • 1.1 冒号语法与 self
    • 1.2 总结
  • 二、实现继承
    • 2.1 单继承实现
    • 2.1 多继承的实现
  • 三、实现封装
  • 四、实现多态


前言

在 Lua 中并没有像传统面向对象语言(如 Java、Python 等)那样内置的类和继承机制,但可以通过表(table)和元表(metatable)来模拟实现类、继承、封装和多态的概念。下面将详细介绍如何实现这些特性,并给出具体的代码示例。


一、实现一个类

在 Lua 中,可以使用表来表示类,通过定义一个构造函数来创建类的实例。

-- 定义一个类
local Person = {}

-- 构造函数
function Person:new(name, age)
	-- 创建一个新的实例
    local instance = {
        name = name,
        age = age
    }
    -- 将 instance 的元表设置为 self(也就是 Person)
    setmetatable(instance, self)
    -- 当在 instance 中查找不存在的键时,会在 self(Person)中查找
    self.__index = self
    -- 返回新创建的实例
    return instance
end

-- 类的方法
function Person:introduce()
    print("My name is " .. self.name .. ", and I'm " .. self.age .. " years old.")
end

-- 创建类的实例
local person1 = Person:new("Alice", 25)
person1:introduce()  -- 输出: My name is Alice, and I'm 25 years old.

1.1 冒号语法与 self

在 Lua 里,使用冒号 : 来定义和调用函数时,会有特殊的参数传递规则。

  • 定义函数:当使用冒号 : 定义函数时,例如 function Person:new(“Alice”, 25),实际上它等价于 function Person:new(self, “Alice”, 25),也就是说 Lua 会自动在参数列表的开头插入一个名为 self 的参数。这里的 self 会指向调用该函数的表(在这个例子中就是 Person 表)。
  • 调用函数:当使用冒号 : 调用函数时,例如 function Person:new(“Alice”, 25),实际上等价于 function Person:new(self, “Alice”, 25),Lua 会自动把调用该函数的表作为第一个参数传递给函数。

1.2 总结

在上述代码中:

  • function Person:new(“Alice”, 25) 被 Lua 内部转换为 function Person:new(self, “Alice”, 25),此时 self 指向 Person 表。
  • setmetatable(instance, self) 这行代码将新创建的实例 instance 的元表设置为 self(即 Person),这样 instance 就可以继承 Person 的方法和属性。
  • self.__index = self 这行代码将 Person 的 __index 元方法设置为 Person 自身,当在 instance 中查找某个不存在的键时,Lua 会通过元表机制在 Person 中查找。

二、实现继承

可以通过修改元表来实现类的继承,子类可以继承父类的方法和属性。

2.1 单继承实现

基本思路

  1. 定义父类:使用表来表示父类,并为其添加属性和方法。
  2. 定义子类:同样使用表来表示子类。
  3. 设置继承关系:通过元表让子类继承父类的属性和方法。
-- 定义一个类
local Person = {}

-- 构造函数
function Person:new(name, age)
    -- 创建一个新的实例
    local instance = {
        name = name,  -- 设置实例的 name 属性
        age = age     -- 设置实例的 age 属性
    }
    -- 将 instance 的元表设置为 self(也就是 Person)
    setmetatable(instance, self)
    -- 当在 instance 中查找不存在的键时,会在 self(Person)中查找
    self.__index = self
    -- 返回新创建的实例
    return instance
end

-- 父类的方法
function Person:introduce()
    -- 打印学生的自我介绍
    print("My name is " .. self.name .. ", I'm " .. self.age .. " years old")
end

-- 子类
local Student = {}
-- 继承 Person 类,设置 Student 的元表为 Person
setmetatable(Student, {__index = Person})

-- 子类的构造函数
function Student:new(name, age, grade)
    -- 调用父类的构造函数创建一个新的实例
    local instance = Person:new(name, age)
    -- 添加子类特有的属性 grade
    instance.grade = grade
    -- 将 instance 的元表设置为 self(也就是 Student)
    setmetatable(instance, self)
    -- 当在 instance 中查找不存在的键时,会在 self(Student)中查找
    self.__index = self
    -- 返回新创建的实例
    return instance
end

-- 子类的方法
function Student:introduce()
    -- 打印学生的自我介绍
    print("My name is " .. self.name .. ", I'm " .. self.age .. " years old, and I'm in grade " .. self.grade .. ".")
end

-- 创建子类的实例
local student1 = Student:new("Bob", 18, 12)
-- 调用子类的方法
student1:introduce()  -- 输出: My name is Bob, I'm 18 years old, and I'm in grade 12.

代码解释

  1. 父类 Person
    • Person 是一个表,代表父类。
    • Person:new 是构造函数,用于创建 Person 类的实例。
    • introduce 是父类的方法。
  2. 子类 Dog
    • setmetatable(Student, {__index = Person}):这行代码设置 Student的元表,使得当在 Student 中查找不存在的键时,会在 Person 中查找,从而实现继承。
    • Student:new 是子类的构造函数,它调用了父类的构造函数来初始化实例。
    • Student:introduce 重写了父类的 speak 方法。

2.1 多继承的实现

基本思路
多重继承意味着一个子类可以有多个父类。实现多重继承的关键在于自定义 __index 元方法,当在子类中查找某个键时,依次在各个父类中查找。

-- 定义第一个父类
local Flying = {}

-- 定义 Flying 类的方法 fly
function Flying:fly()
    -- 打印当前实例的 name 属性,表示正在飞行
    print(self.name .. " is flying.")
end

-- 定义第二个父类
local Swimming = {}

-- 定义 Swimming 类的方法 swim
function Swimming:swim()
    -- 打印当前实例的 name 属性,表示正在游泳
    print(self.name .. " is swimming.")
end

-- 定义子类
local Duck = {}

-- 多重继承辅助函数
local function search(k, parents)
    -- 遍历父类列表
    for _, parent in ipairs(parents) do
        -- 在父类中查找键 k
        local v = parent[k]
        -- 如果找到,则返回对应的方法或属性
        if v then return v end
    end
end

-- 设置 Duck 的元表
function Duck:new(name)
    -- 创建一个新的实例,并初始化 name 属性
    local instance = {
        name = name
    }
    -- 定义父类列表
    local parents = {Flying, Swimming}
    -- 设置实例的元表
    setmetatable(instance, {
        -- 当在实例中查找不存在的键时,调用 __index 函数
        __index = function(t, k)
            -- 在父类列表中查找键 k
            return search(k, parents)
        end
    })
    -- 返回新创建的实例
    return instance
end

-- 创建子类实例
local duck = Duck:new("Donald")
-- 调用从 Flying 类继承的 fly 方法
duck:fly()  -- 输出: Donald is flying.
-- 调用从 Swimming 类继承的 swim 方法
duck:swim() -- 输出: Donald is swimming.

代码解释

  1. 父类 Flying 和 Swimming:分别定义了 fly 和 swim 方法。
  2. 子类 Duck
    • search 函数用于在多个父类中查找某个键。
    • Duck:new 是子类的构造函数,它创建了一个实例,并设置了自定义的 __index 元方法。当在 duck 实例中查找某个键时,会调用 __index 元方法,该方法会依次在 Flying 和 Swimming 中查找。

三、实现封装

在 Lua 中,可以通过闭包来实现封装,将一些属性和方法隐藏起来,只提供公共的接口。

local function createPerson(name, age)
    -- 私有属性
    local privateName = name
    local privateAge = age

    -- 公共接口
    local public = {}

    function public:introduce()
        print("My name is " .. privateName .. ", and I'm " .. privateAge .. " years old.")
    end

    return public
end

-- 创建实例
local person2 = createPerson("Charlie", 30)
person2:introduce()  -- 输出: My name is Charlie, and I'm 30 years old.

四、实现多态

多态是指不同的对象对同一消息做出不同的响应。在 Lua 中,可以通过继承和方法重写来实现多态。

-- 父类 Shape
local Shape = {}  -- 定义一个空表 Shape,作为父类

-- Shape 的构造函数
function Shape:new()
    local instance = {}  -- 创建一个新的实例表
    setmetatable(instance, self)  -- 将实例的元表设置为 Shape
    self.__index = self  -- 设置 Shape 的 __index 元方法为自身,用于方法查找
    return instance  -- 返回新创建的实例
end

-- Shape 的 area 方法(默认实现,返回 0)
function Shape:area()
    return 0  -- 默认面积为 0
end

-- 子类:圆形 Circle
local Circle = {}  -- 定义一个空表 Circle,作为子类
setmetatable(Circle, {__index = Shape})  -- 设置 Circle 的元表为 Shape,实现继承

-- Circle 的构造函数
function Circle:new(radius)
    local instance = Shape:new()  -- 调用父类的构造函数创建实例
    instance.radius = radius  -- 添加 radius 属性
    setmetatable(instance, self)  -- 将实例的元表设置为 Circle
    self.__index = self  -- 设置 Circle 的 __index 元方法为自身
    return instance  -- 返回新创建的实例
end

-- Circle 的 area 方法(重写父类方法)
function Circle:area()
    return math.pi * self.radius * self.radius  -- 计算并返回圆形的面积
end

-- 子类:矩形 Rectangle
local Rectangle = {}  -- 定义一个空表 Rectangle,作为子类
setmetatable(Rectangle, {__index = Shape})  -- 设置 Rectangle 的元表为 Shape,实现继承

-- Rectangle 的构造函数
function Rectangle:new(width, height)
    local instance = Shape:new()  -- 调用父类的构造函数创建实例
    instance.width = width  -- 添加 width 属性
    instance.height = height  -- 添加 height 属性
    setmetatable(instance, self)  -- 将实例的元表设置为 Rectangle
    self.__index = self  -- 设置 Rectangle 的 __index 元方法为自身
    return instance  -- 返回新创建的实例
end

-- Rectangle 的 area 方法(重写父类方法)
function Rectangle:area()
    return self.width * self.height  -- 计算并返回矩形的面积
end

-- 多态示例:打印任意形状的面积
local function printArea(shape)
    print("The area of the shape is: " .. shape:area())  -- 调用 shape 的 area 方法并打印结果
end

-- 创建 Circle 和 Rectangle 的实例
local circle = Circle:new(5)  -- 创建一个半径为 5 的圆形
local rectangle = Rectangle:new(4, 6)  -- 创建一个宽 4、高 6 的矩形

-- 调用 printArea 函数,展示多态行为
printArea(circle)  -- 输出: The area of the shape is: 78.539816339745
printArea(rectangle)  -- 输出: The area of the shape is: 24

代码总结

  1. 类的定义:

    • 使用表({})表示类。
    • 通过 setmetatable 和 __index 实现继承和方法查找。
  2. 构造函数

    • 使用 new 方法创建实例。
    • 在构造函数中调用父类的构造函数(如 Shape:new())。
  3. 方法重写

    • 子类可以重写父类的方法(如 Circle:area 和 Rectangle:area)。
  4. 多态

    • 通过统一的接口(如 shape:area())调用不同子类的方法。
  5. 代码结构

    • 父类提供默认实现,子类扩展或重写父类行为。
    • 通过函数(如 printArea)展示多态特性。

你可能感兴趣的:(lua,lua,开发语言)