Menu
CoddyTech

鸭子类型

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

Lua 不在乎对象的类。它只关心对象能做什么。这个概念叫作鸭子类型:“如果它走起来像鸭子,叫起来也像鸭子,那它就是鸭子。”用编程术语来说,只要对象有你正在调用的方法,它就能正常工作。

这意味着完全不相关的类也可以一起使用,只要它们共享相同的方法名:

local Circle = {}
Circle.__index = Circle
function Circle:new() 
    local obj = {}
    setmetatable(obj, Circle)
    return obj
end
function Circle:draw()
    print("Drawing a circle")
end

local Square = {}
Square.__index = Square
function Square:new()
    local obj = {}
    setmetatable(obj, Square)
    return obj
end
function Square:draw()
    print("Drawing a square")
end

这些类之间没有继承关系,它们完全独立。不过,我们可以用相同的方式处理它们:

local shapes = {Circle:new(), Square:new(), Circle:new()}

for _, shape in ipairs(shapes) do
    shape:draw()
end
-- 输出:
-- 正在绘制一个圆形
-- 正在绘制一个正方形
-- 正在绘制一个圆形

循环不会检查每个对象的类型。它只调用 :draw(),并相信这个方法存在。这种灵活性很强。你可以添加新的形状类型,而完全不必修改循环代码。只要新类实现了 :draw(),就能正常工作。

challenge icon

挑战

简单

让我们通过构建一个通知系统来展示鸭子类型的强大之处:系统中的类彼此完全无关,却都能用相同的方式处理!

你将创建三个相互独立的通知类,它们之间没有继承关系,但都实现了 :send() 方法。这意味着你可以遍历一个混合集合,并对其中每个对象调用 :send(),而无需关心它的实际类型。

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

  • Email.lua:一个类,包含用于保存邮件收件人的 :new(recipient) 构造函数。它的 :send() 方法应输出 Emailing {recipient}。
  • SMS.lua:一个完全独立的类,包含 :new(phoneNumber) 构造函数。它的 :send() 方法应输出 Texting {phoneNumber}。
  • PushNotification.lua:另一个独立的类,包含 :new(deviceId) 构造函数。它的 :send() 方法应输出 Pushing to {deviceId}。
  • main.lua:把所有内容组合起来!读取三个输入,为每种通知类型各创建一个实例,把它们放进同一个表中,然后遍历表并对每个实例调用 :send()。

你会收到三个输入:

  1. 邮件收件人(例如,alice@example.com)
  2. 电话号码(例如,555-1234)
  3. 设备 ID(例如,device_99)

在主文件中,创建三个通知对象,把它们存入一个表中,然后遍历这个表,对每个对象调用 :send()。顺序应为:先 Email,然后 SMS,最后 PushNotification。

例如,如果输入是 bob@mail.com、555-9876 和 phone_42,输出应为:

Emailing bob@mail.com
Texting 555-9876
Pushing to phone_42

注意,你的循环不需要检查每个对象的类型。它只需相信集合中的每个对象都有 :send() 方法。这就是鸭子类型的实际应用!只要它能 :send(),它就是一种通知。

亲手试试

-- main.lua
-- 汇总所有通知类型,演示鸭子类型

-- 导入通知类
local Email = require('Email')
local SMS = require('SMS')
local PushNotification = require('PushNotification')

-- 读取输入
local recipient = io.read()
local phoneNumber = io.read()
local deviceId = io.read()

-- TODO: 为每种通知类型创建一个实例

-- TODO: 把所有通知放进同一个表(先是 Email,然后是 SMS,最后是 PushNotification)

-- TODO: 遍历表,对每条通知调用 :send()
-- 这演示了鸭子类型:我们不检查类型,只调用 :send()
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Lua 在线编译器