Menu
CoddyTech

鸭子类型

本课属于 Coddy GDScript 学习旅程的“面向对象编程”单元。第 22 课,共 65 课。

鸭子类型关注对象能做什么,而不是它属于哪个类。只要它有你调用的方法,这次调用就能正常工作。这个名称源自一句俗语:“如果它游起来像鸭子,叫起来也像鸭子,那它就是鸭子。”

这两个类没有共同的父类,但它们都有 swim() 和 quack():

# duck.gd
class_name Duck
extends RefCounted

func swim() -> String:
	return "Duck swimming"

func quack() -> String:
	return "Quack!"
# robot.gd
class_name Robot
extends RefCounted

func swim() -> String:
	return "Robot swimming with propellers"

func quack() -> String:
	return "Robot sound: BEEP BEEP!"

不带类型的参数可以接受任何对象。函数会调用这些方法,每个对象都会运行自己的方法:

func make_it_swim_and_quack(thing) -> void:
	print(thing.swim())
	print(thing.quack())
make_it_swim_and_quack(Duck.new())
make_it_swim_and_quack(Robot.new())
Duck swimming
Quack!
Robot swimming with propellers
Robot sound: BEEP BEEP!

重写需要一个共同的父类,鸭子类型则不需要。参数的类型标注为 thing: Duck 时,传入 Robot 会导致解析错误。

如果对象缺少该方法,调用就会因错误而停止函数运行,例如 Invalid call. Nonexistent function 'quack'。先用 has_method() 检查,该方法接受方法名作为 String:

var things := [Duck.new(), Rock.new()]
for thing in things:
	if thing.has_method("quack"):
		print(thing.quack())
	else:
		print("This one cannot quack")
Quack!
This one cannot quack

价格:使用类型时,Godot 会在程序运行前发现错误的方法名;使用鸭子类型时,只有运行到那一行才会发现。对于属于无关类但共享少数方法的对象,可以使用鸭子类型。

challenge icon

挑战

简单

爆炸会对附近所有能受到伤害的对象造成伤害。这些对象没有共同的父类。player.gd 和 rock.gd 已完成:Player 有 take_damage(amount) 和 status(),Rock 只有 status()。

  • 在 crate.gd 中,为 Crate 添加一个初始值为 false 的 broken 变量、一个在伤害值达到或超过 10 时摧毁箱子的 take_damage(amount) 方法,以及一个返回 Crate: broken 或 Crate: intact 的 status()。
  • 在 explosion.gd 中,编写静态函数 hit_all(things, amount)。它会对数组中拥有 take_damage(amount) 方法的每个对象调用该方法,并返回命中的对象数量。

提供的代码会读取一行对象名称(player、crate 或 rock),然后读取后续爆炸的数量以及每次爆炸的伤害值。它会打印每次爆炸命中的对象数量,然后打印每个对象的状态。输入 player crate rock,并设置两次爆炸的伤害值分别为 5 和 20 时,输出如下:

Explosion 1 hit 2 objects
Explosion 2 hit 2 objects
Player: 75 HP
Crate: broken
Rock: solid

你的代码写在 crate.gd 和 explosion.gd 中。main.gd 保存了提供的输入/输出代码,不能编辑。

亲手试试

extends Node

func _ready():
	# 提供的输入/输出代码:保持原样
	var things := []
	for word in OS.read_string_from_stdin().split(" "):
		if word == "player":
			things.append(Player.new())
		elif word == "crate":
			things.append(Crate.new())
		else:
			things.append(Rock.new())
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var hits := Explosion.hit_all(things, int(OS.read_string_from_stdin()))
		print("Explosion %d hit %d objects" % [i + 1, hits])
	for thing in things:
		print(thing.status())
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: GDScript 在线编译器