Menu
CoddyTech

Утиная типизация

Часть раздела Объектно-ориентированное программирование путешествия по GDScript на Coddy. Урок 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 переменную broken, изначально равную false, метод take_damage(amount), который ломает ящик, если урон равен 10 или больше, и метод status(), возвращающий Crate: broken или Crate: intact.
  • В explosion.gd напиши статический метод hit_all(things, amount). Он вызывает take_damage(amount) у каждого объекта в Array, у которого есть этот метод, и возвращает количество объектов, по которым он попал.

Предоставленный код считывает строку с именами объектов (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