Menu
CoddyTech

Абстрактные классы и методы

Часть раздела Объектно-ориентированное программирование путешествия по GDScript на Coddy. Урок 23 из 65.

Некоторые классы существуют только для наследования. У любой фигуры есть площадь, но вычислить её нельзя, пока неизвестно, является ли фигура кругом или прямоугольником. Пометь такой класс как @abstract, на строке выше class_name:

# shape.gd
@abstract
class_name Shape
extends RefCounted

@abstract func area() -> float
@abstract func perimeter() -> float

func describe() -> String:
	return "Area %.1f, perimeter %.1f" % [area(), perimeter()]

У @abstract func есть сигнатура, но нет тела: после неё нет двоеточия и кода. Это означает, что каждый дочерний класс должен предоставить этот метод. describe() — обычный метод; дочерние классы наследуют его, и он уже может вызывать абстрактные методы.

Нельзя создать объект абстрактного класса. Shape.new() останавливает программу до её запуска с ошибкой Parse Error: Cannot construct abstract class "Shape".

Дочерний класс, в котором реализован каждый абстрактный метод, является обычным классом:

# rectangle.gd
class_name Rectangle
extends Shape

var width: float
var height: float

func _init(w: float, h: float) -> void:
	width = w
	height = h

func area() -> float:
	return width * height

func perimeter() -> float:
	return 2 * (width + height)
var rect := Rectangle.new(4.0, 6.0)
print(rect.describe())
Area 24.0, perimeter 20.0

Если дочерний класс пропустит один из них, Godot сообщит об этом до запуска: Class "Square" must implement "Shape.perimeter()" and other inherited abstract methods or be marked as "@abstract". Поэтому у каждого объекта, который можно создать, есть area() и perimeter().

Абстрактный класс всё ещё может иметь переменные и _init(). Дочерние классы вызывают его с помощью super(), как обычно.

challenge icon

Задание

Легко

Магазин принимает два способа оплаты. У каждого платежа есть сумма и чек, но комиссия и название зависят от способа оплаты.

  • Сделай PaymentMethod абстрактным классом и объяви два абстрактных метода: fee() -> float и method_name() -> String. Напиши receipt(), который возвращает NAME: paid AMOUNT, fee FEE, total TOTAL, выводя каждое число с двумя знаками после запятой (итог — это сумма платежа плюс комиссия).
  • CreditCard: комиссия составляет 3% от суммы (amount * 0.03), а название — Credit card.
  • BankTransfer: комиссия составляет 1.5 или 0.0, если сумма равна 100 или больше; название — Bank transfer.

Дочерние классы используют конструктор PaymentMethod. Предоставленный код считывает количество последующих платежей, а затем по одной строке на каждый платёж: card AMOUNT или transfer AMOUNT. Он выводит каждый чек и сумму комиссий. Для card 50 и transfer 120 он выводит:

Credit card: paid 50.00, fee 1.50, total 51.50
Bank transfer: paid 120.00, fee 0.00, total 120.00
Total fees: 1.50

Твой код нужно поместить в payment_method.gd, credit_card.gd и bank_transfer.gd. В main.gd находится предоставленный код ввода/вывода, и его нельзя редактировать.

Попробуйте сами

extends Node

func _ready():
	# Предоставленный код ввода-вывода: оставьте его без изменений
	var count := int(OS.read_string_from_stdin())
	var total_fees := 0.0
	for i in count:
		var p := OS.read_string_from_stdin().split(" ")
		var payment: PaymentMethod
		if p[0] == "card":
			payment = CreditCard.new(float(p[1]))
		else:
			payment = BankTransfer.new(float(p[1]))
		print(payment.receipt())
		total_fees += payment.fee()
	print("Total fees: %.2f" % total_fees)
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор GDScript