Menu
CoddyTech

扩展构造函数

本课属于 Coddy Lua 学习旅程的“面向对象编程”单元。第 33 课,共 70 课。

子类继承父类时,你通常希望子类的构造函数初始化父类需要初始化的所有内容,以及子类特有的其他字段。你可以直接调用父类的构造函数,而不必重复编写代码。

这种技术会使用父类名称来调用它的 :new() 方法,并将 self 作为第一个参数传入。这样会创建一个包含所有父类初始化内容的基础对象,然后你可以针对子类进行自定义。

local Vehicle = {}
Vehicle.__index = Vehicle

function Vehicle:new(speed)
    local obj = {speed = speed}
    setmetatable(obj, Vehicle)
    return obj
end

-- Car 继承自 Vehicle
local Car = {}
Car.__index = Car
setmetatable(Car, {__index = Vehicle})

function Car:new(speed, brand)
    local obj = Vehicle.new(Vehicle, speed)  -- 调用父类构造函数
    obj.brand = brand                         -- 添加子类特有的字段
    setmetatable(obj, Car)                    -- 重新关联到 Car
    return obj
end

注意子类构造函数中的关键步骤:首先,我们调用 Vehicle.new(Vehicle, speed),获取一个已设置 speed 字段的对象。然后添加子类专属的 brand 字段。最后,将元表从 Vehicle 改为 Car,这样实例会优先在子类中查找方法。

调用父构造函数时,你也可以使用冒号语法:Vehicle:new(speed) 等同于 Vehicle.new(Vehicle, speed),因为两种写法都会将 Vehicle 作为第一个参数传入。在这里,两种写法都能正常工作,所以你可以选择自己觉得更易读的写法。

local myCar = Car:new(120, "Toyota")
print(myCar.speed)  -- 输出:120
print(myCar.brand)  -- 输出:Toyota

这种模式可以让你的代码保持 DRY:共享的初始化逻辑放在父类中,而每个子类只处理各自独有的属性。

challenge icon

挑战

简单

让我们构建一个生物类层次结构,让子类扩展父类的构造函数!你将创建一个 Animal 父类和一个 Bird 子类。Bird 的构造函数会先调用 Animal 的构造函数来处理共享的初始化,再添加它独有的属性。

你将把代码分到三个文件中:

  • Animal.lua:定义父类,其中包含一个用于存储动物名称的 :new(name) 构造函数。添加一个会打印动物名称的 :describe() 方法。使用标准的类模式设置 __index,并返回这个类。
  • Bird.lua:定义继承自 Animal 的子类。Bird 的 :new(name, wingspan) 构造函数应使用 Animal.new(Animal, name) 调用父类构造函数来初始化名称,然后将 wingspan 字段添加到对象中,最后将元表重新关联到 Bird。添加一个以指定格式打印名称和翼展的 :info() 方法。
  • main.lua:引入 Bird 模块,读取两个输入(名称和翼展),创建一个 Bird 实例,并调用两个方法,演示继承功能和子类特有功能如何协同工作。

你会收到两个输入:

  1. 鸟的名称
  2. 鸟的翼展(一个数字)

在主文件中,使用给定的名称和翼展创建一个 Bird。然后在单独的行上依次调用 :describe() 和 :info()。

(从 Animal 继承的):describe() 方法应打印:

Animal: {name}

(在 Bird 中定义的):info() 方法应打印:

{name} has a wingspan of {wingspan}

例如,如果输入是 Eagle 和 200,输出应为:

Animal: Eagle
Eagle has a wingspan of 200

关键在于,Bird 构造函数不会重复初始化名称,而是把这项工作交给 Animal 的构造函数,然后只添加鸟类独有的内容。这样可以让代码保持 DRY,同时构建正确的继承链!

亲手试试

-- main.lua:入口

local Bird = require('Bird')

-- 读取输入
local name = io.read()
local wingspan = tonumber(io.read())

-- TODO:使用给定的 name 和 wingspan 创建一个 Bird 实例

-- TODO:调用鸟的 :describe() 方法(继承自 Animal)

-- TODO:调用鸟的 :info() 方法(在 Bird 中定义)
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Lua 在线编译器