方法重写和多态
重写父类方法,并实现运行时多态分派。
方法重写和多态 是 CoddyKit 上的免费 Lua Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Lua Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Lua Academy 课程共包含 4 节课。
方法重写
子类通过定义同名方法来重写父类方法。当通过子类实例调用该方法时,会执行子类版本。仍可通过 Parent.method(self) 访问父类版本。
local Shape={} Shape.__index=Shape
function Shape:area() return 0 end
function Shape:describe() print(type(self).." area="..self:area()) end
local Circle={} Circle.__index=Circle
setmetatable(Circle,{__index=Shape})
function Circle.new(r) return setmetatable({r=r},Circle) end
function Circle:area() return math.pi*self.r^2 end
local Square={} Square.__index=Square
setmetatable(Square,{__index=Shape})
function Square.new(s) return setmetatable({s=s},Square) end
function Square:area() return self.s^2 end
Circle.new(5):describe()
Square.new(4):describe()多态分派
多态是指不同类型的对象以不同方式响应同一消息。接受任意“形状”并调用 :area() 的函数可以处理任何形状类型——每种形状都会使用自己的实现进行响应。
local shapes={
Circle.new(3),
Square.new(4),
Circle.new(1),
}
local total=0
for _,shape in ipairs(shapes) do
total=total+shape:area()
end
print(string.format("Total area: %.2f",total))鸭子类型
Lua 使用鸭子类型:只要对象拥有某个方法,就可以使用它。不需要正式声明接口。调用 :speak() 的函数可以处理任何拥有 speak 方法的对象,而不论其所属的类是什么。
local function makeNoise(obj)
if type(obj.speak)=="function" then
obj:speak()
else
print("(silent)")
end
end
local Duck={speak=function(self) print("Quack!") end}
local Robot={speak=function(self) print("Beep boop!") end}
local Rock={}
makeNoise(Duck) -- Quack!
makeNoise(Robot) -- Beep boop!
makeNoise(Rock) -- (silent)运算符多态
像 __add 这样的元方法可以实现运算符多态。只要定义了 __add,不同类型就能以各自的语义响应 +。
local Vec2={} Vec2.__index=Vec2
Vec2.__add=function(a,b) return Vec2.new(a.x+b.x,a.y+b.y) end
Vec2.__tostring=function(v) return "("..v.x..","..v.y..")" end
function Vec2.new(x,y) return setmetatable({x=x,y=y},Vec2) end
local Color={} Color.__index=Color
Color.__add=function(a,b)
return Color.new(math.min(255,a.r+b.r),math.min(255,a.g+b.g),math.min(255,a.b+b.b))
end
function Color.new(r,g,b) return setmetatable({r=r,g=g,b=b},Color) end
print(tostring(Vec2.new(1,2)+Vec2.new(3,4))) -- (4,6)访问者模式
访问者模式将操作与对象分离。某个操作会“访问”不同类型的对象,并根据类型调用相应的方法。对于需要按类型变化、但又不应修改这些类型的操作,可以使用此模式。
local function printInfo(obj)
local mt = getmetatable(obj)
if mt == Circle then
print(string.format("Circle r=%.1f area=%.2f",obj.r,obj:area()))
elseif mt == Square then
print(string.format("Square s=%.1f area=%.2f",obj.s,obj:area()))
end
end
for _,s in ipairs({Circle.new(3),Square.new(4)}) do
printInfo(s)
end接口表
将接口定义为方法名称表。在以多态方式使用对象之前,验证对象是否实现了所有必需的方法。
local function implements(obj, interface)
for _, method in ipairs(interface) do
if type(obj[method]) ~= "function" then
return false, "missing: "..method
end
end
return true
end
local Drawable = {"draw","resize","move"}
local Widget = {
draw=function(self) print("draw "..self.type) end,
resize=function(self,w,h) self.w,self.h=w,h end,
move=function(self,x,y) self.x,self.y=x,y end,
type="button"
}
local ok,err = implements(Widget,Drawable)
print(ok,err) -- true nil方法存在性检查
以多态方式调用可选方法之前,请检查对象是否拥有该方法。这样无需要求所有对象都继承同一个基类,也能支持可选能力。
local function maybeUpdate(obj, dt)
if type(obj.update)=="function" then
obj:update(dt)
end
end
local objects={
{x=0,y=0,update=function(self,dt) self.x=self.x+10*dt end},
{x=0,y=0}, -- no update
{x=0,y=0,update=function(self,dt) self.y=self.y+5*dt end},
}
for _,o in ipairs(objects) do maybeUpdate(o,1) end
for _,o in ipairs(objects) do print(o.x,o.y) end基于类型的分派表
不要使用冗长的 if/elseif 链,而应使用分派表:将类型名称映射到处理函数。这就是表驱动的多态模式。
local handlers={
number = function(v) return "num:"..v end,
string = function(v) return "str:\"".."..v.."\"" end,
boolean = function(v) return "bool:"..(v and "T" or "F") end,
table = function(v) return "tbl:#"..tostring(#v) end,
}
local function describe(v)
local h=handlers[type(v)]
return h and h(v) or "unknown"
end
print(describe(42))
print(describe("hello"))
print(describe({1,2,3}))协变返回值
子类方法可以返回比父类方法更具体的类型(协变返回)。Lua 采用动态类型,因此这很自然——子类的工厂函数返回子类实例,而不只是父类实例。
local Base={} Base.__index=Base
function Base.new() return setmetatable({type="base"},Base) end
function Base:copy() return Base.new() end
local Child={} Child.__index=Child
setmetatable(Child,{__index=Base})
function Child.new(extra)
local self=setmetatable({type="child"},Child)
self.extra=extra; return self
end
function Child:copy() return Child.new(self.extra) end -- covariant
local c=Child.new("hello")
local c2=c:copy()
print(c2.extra,getmetatable(c2)==Child) -- hello true多态总结
Lua 中的多态实践是:同一个方法名称,根据类型产生不同的行为。它可以通过基于元表的面向对象编程、鸭子类型检查或分派表实现。不需要正式的接口系统,只需遵循一致的方法命名约定即可。
-- Three ways to achieve polymorphism:
-- 1. Metatable inheritance + method override
-- 2. Duck typing: just call it if it exists
-- 3. Dispatch table: map type -> handler
local function area(shape)
-- Duck typing approach:
assert(type(shape.area)=="function","needs area()")
return shape:area()
end
print(area({area=function() return 16 end})) -- 16多态问题
Lua 中的多态是如何工作的?
回顾:重写与多态
在子类中重新定义父类方法,即可重写这些方法。Lua 会沿着 __index 链从底向上解析调用,从而自然地实现运行时多态分派。
常见问题解答
「方法重写和多态」课时是免费的吗?
是的 — 「方法重写和多态」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Lua Academy 课程的其余内容,请升级到 CoddyKit PRO。 Lua Academy 课程共包含 4 节课。
「方法重写和多态」这节课中我会学到什么?
重写父类方法,并实现运行时多态分派。 你通过在浏览器中直接运行的动手代码来练习 Lua Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Lua Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Lua Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「方法重写和多态」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Lua Academy 课中编写并运行代码吗?
能。每节 Lua Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。