调用父类方法
本课属于 Coddy Lua 学习旅程的“面向对象编程”单元。第 38 课,共 70 课。
有时,你并不想完全重写一个方法。你可能想要扩展父类的行为:先执行父类的操作,再添加一些额外内容。为此,你可以在子类的重写方法中直接调用父类的方法。
这种写法使用父类名称加点号的语法,并将 self 显式作为第一个参数传入:
local Animal = {}
Animal.__index = Animal
function Animal:new(name)
local obj = {name = name}
setmetatable(obj, Animal)
return obj
end
function Animal:speak()
print(self.name .. " makes a sound")
end
-- Dog 扩展了父类的行为
local Dog = {}
Dog.__index = Dog
setmetatable(Dog, {__index = Animal})
function Dog:new(name)
local obj = Animal.new(Animal, name)
setmetatable(obj, Dog)
return obj
end
function Dog:speak()
Animal.speak(self) -- 先调用父类方法
print(self.name .. " wags tail happily")
end关键代码行是 Animal.speak(self)。通过对父类使用点号语法并手动传入 self,我们会先调用原始方法,再添加自己的行为:
local buddy = Dog:new("Buddy")
buddy:speak()
-- 输出:
-- Buddy 发出叫声
-- Buddy 开心地摇尾巴当父类方法执行了你不想重复的重要工作时,这种模式就很有用。子类可以先执行父类的逻辑,最后执行,甚至在自己的代码中途执行,让你完全掌控不同操作的组合方式。
挑战
简单让我们构建一个问候系统,让 FormalPerson 扩展普通 Person 的行为!正式版本不会完全替换一个人自我介绍的方式,而是先进行标准的自我介绍,然后再加上一句礼貌的话。
你要将代码分成三个文件:
Person.lua:这是基类,包含一个用于保存姓名的:new(name)构造函数。添加一个:introduce()方法,打印这个人的姓名,后面接上says hi。这是将被扩展的基础行为。FormalPerson.lua:这是继承自 Person 的子类。它的构造函数应调用父类构造函数来处理姓名。重写:introduce()方法,但不要完全替换父类的行为,先调用父类的方法,使用Person.introduce(self),然后再打印一行,内容是这个人的姓名,后面接上bows politely。main.lua:引入你的 FormalPerson 模块,从输入中读取姓名,创建一个 FormalPerson 实例,然后调用:introduce(),查看两种行为组合后的效果。
你会收到一个输入:这个人的姓名。
对 FormalPerson 调用 :introduce() 时,应输出两行:第一行来自父类的方法,第二行来自子类添加的内容:
{name} says hi
{name} bows politely例如,如果输入是 Eleanor,输出应为:
Eleanor says hi
Eleanor bows politely关键在于,FormalPerson 不会重复实现“says hi”的逻辑。它会把这部分工作交给父类的方法,然后在此基础上添加自己的独特行为。这样可以让代码更简洁,也展示了子类如何扩展父类的功能,而不是替换它!
亲手试试
-- main.lua:问候系统的入口
local FormalPerson = require('FormalPerson')
-- 从输入中读取此人的姓名
local name = io.read()
-- TODO:用给定的姓名创建一个 FormalPerson 实例
-- TODO:调用 FormalPerson 实例的 introduce 方法
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Lua 在线编译器