Menu
CoddyTech

混入与继承

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

你已经学会了两种共享行为的方式:继承通过原型链建立“是一个”的关系,而混入会将函数直接复制到类中。那么,应该在什么时候使用这两种方式呢?

当对象共享一种基本身份时,使用继承。Bird 是一种 Animal。让 Bird 继承动物的核心行为是合理的,比如 :eat() 或 :sleep()。这种关系是永久的,并定义了对象的本质。

当你想添加一种不会定义对象核心身份的能力时,可以使用 混入。

并非所有鸟都会飞:企鹅和鸵鸟都是鸟,但它们不会飞。飞行是一种能力,而不是鸟类的定义特征。Flyable 混入类可以让你为需要飞行能力的鸟添加飞行功能,而不强迫所有鸟都具备这种能力。

这两种方法可以协同工作。这里,Eagle 和 Penguin 都继承自 Bird,只有 Eagle 会获得这个混入:

-- Bird 继承自 Animal(is-a 关系)
local Bird = {}
Bird.__index = Bird
setmetatable(Bird, {__index = Animal})

-- Eagle 和 Penguin 都是鸟
local Eagle = setmetatable({}, {__index = Bird})
Eagle.__index = Eagle
local Penguin = setmetatable({}, {__index = Bird})
Penguin.__index = Penguin

-- Flyable 是一种能力(混入)
local Flyable = {}
function Flyable:fly()
    print(self.name .. " soars through the sky!")
end

-- 只将混入应用于会飞的鸟
applyMixin(Eagle, Flyable)

鹰可以调用 :fly()。企鹅不行:fly 不在 Penguin、Bird 或 Animal 中,因此 penguin:fly() 会报错。如果改为将混入应用于 Bird,就会把 fly 复制到所有鸟类中,包括企鹅。

challenge icon

挑战

简单

让我们构建一个小型生物生态系统,来演示何时该使用继承,何时该使用 mixin!你将创建一个 Animal 基类、一个继承自它的 Bird 类,以及一个 Swimmable mixin,因为虽然所有鸟都是动物,但并不是所有鸟都会游泳。

你将把代码分成 4 个文件:

  • Animal.lua:创建 Animal 基类,并通过构造函数 :new(name) 保存名称。添加一个 :eat() 方法,打印 {name} is eating。这代表所有动物共有的核心特征。
  • Swimmable.lua:创建一个 mixin,其中包含 :swim() 方法,用于打印 {self.name} is swimming。游泳是一种能力,并不是所有生物都会游泳,因此它不应该属于继承链的一部分。
  • Bird.lua:创建一个继承自 Animal 的 Bird 类。构造函数 :new(name) 应该调用父类构造函数。添加一个 :chirp() 方法,用于打印 {name} chirps!。这是鸟类特有的行为。不要把游泳能力放进 Bird:并不是所有鸟都会游泳。
  • main.lua:在这里编写辅助函数 applyMixin(target, mixin),它会把 mixin 中的每个函数复制到目标对象中。读取鸟的名称,以及它是否会游泳(yes 或 no)。创建一个 Bird 对象,如果它会游泳,就只对这只鸟应用 Swimmable mixin。接着调用 :eat()(继承自 Animal)和 :chirp()(Bird 自己的方法),并且只有当这只鸟有 :swim() 方法时才调用它(if bird.swim then)。

你会收到两个输入:

  1. 鸟的名称(一个字符串)
  2. 鸟是否会游泳:yes 或 no

如果鸟会游泳,输出应为:

{name} is eating
{name} chirps!
{name} is swimming

如果鸟不会游泳,输出应为:

{name} is eating
{name} chirps!

例如,如果输入是 Duck 和 yes,输出应为:

Duck is eating
Duck chirps!
Duck is swimming

如果输入是 Sparrow 和 no,输出应为:

Sparrow is eating
Sparrow chirps!

这个挑战展示了关键区别:Bird 是一种 Animal(继承),而游泳是一种可选的能力(mixin)。由于 mixin 只会添加到会游泳的鸟身上,不会游泳的鸟根本没有 swim 方法。

亲手试试

-- main.lua:入口文件,创建一只鸟并展示它的能力

-- 将 mixin 应用到类或单个对象的辅助函数
local function applyMixin(target, mixin)
    -- TODO:将 mixin 中的所有函数复制到 target

end

local Bird = require('Bird')
local Swimmable = require('Swimmable')

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

-- TODO:使用输入的名字创建一个 Bird 实例

-- TODO:如果输入是 "yes",只将 Swimmable mixin 应用到这只鸟

-- TODO:调用从 Animal 继承的 eat()

-- TODO:调用 Bird 自己的 chirp() 方法

-- TODO:只有这只鸟有 swim() 方法时才调用它
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Lua 在线编译器