混入与继承
本课属于 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 复制到所有鸟类中,包括企鹅。
挑战
简单让我们构建一个小型生物生态系统,来演示何时该使用继承,何时该使用 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对象,如果它会游泳,就只对这只鸟应用Swimmablemixin。接着调用:eat()(继承自Animal)和:chirp()(Bird自己的方法),并且只有当这只鸟有:swim()方法时才调用它(if bird.swim then)。
你会收到两个输入:
- 鸟的名称(一个字符串)
- 鸟是否会游泳:
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() 方法时才调用它
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Lua 在线编译器