Menu
CoddyTech

Паттерн «Стратегия»

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

Паттерн Strategy помещает каждую версию алгоритма в отдельный объект, поэтому ты можешь менять алгоритм во время работы программы. Объект, который его использует, — контекст — вызывает один метод и не заботится о том, какую версию он хранит.

Вот две стратегии атаки: родительский класс и подкласс с одинаковым методом damage():

# attack_style.gd
class_name AttackStyle
extends RefCounted

func damage(power: int) -> int:
	return power
# heavy_attack.gd
class_name HeavyAttack
extends AttackStyle

func damage(power: int) -> int:
	return power * 2

Контекст хранит стратегию и передаёт ей выполнение вычисления:

# fighter.gd
class_name Fighter
extends RefCounted

var power := 10
var style: AttackStyle = AttackStyle.new()

func set_style(new_style: AttackStyle) -> void:
	style = new_style

func attack() -> void:
	print("Hit for ", style.damage(power))
# main.gd
var fighter := Fighter.new()
fighter.attack()
fighter.set_style(HeavyAttack.new())
fighter.attack()
Hit for 10
Hit for 20

Fighter никогда не проверяет, какой стиль у него есть. Новый стиль — это новый класс, а fighter.gd остаётся без изменений; цепочка if style == "heavy" внутри attack() разрасталась бы с каждым новым стилем.

Если стратегия представляет собой одну небольшую функцию без собственных данных, достаточно Callable. Контекст хранит функцию и запускает её с помощью call():

# price_calculator.gd
class_name PriceCalculator
extends RefCounted

var discount: Callable = func(price): return price

func final_price(price: int) -> int:
	return discount.call(price)
# main.gd
var calc := PriceCalculator.new()
print(calc.final_price(80))
calc.discount = func(price): return price / 2
print(calc.final_price(80))
calc.discount = func(price): return max(price - 25, 0)
print(calc.final_price(80))
80
40
55

Используй классы, если у стратегии есть собственные настройки или несколько методов, а для одного короткого вычисления — вызываемые объекты (Callables). Ты уже использовал эту идею: функция сравнения, которую ты передаёшь в sort_custom(), — это стратегия сортировки.

challenge icon

Задание

Средне

Корзина магазина может переключать стратегии скидок при оформлении заказа. Discount в discount.gd — базовая стратегия: без скидки. Напиши ещё две стратегии и корзину:

  • PercentOff (создаётся с процентом): apply(total) возвращает total - total * percent / 100 (целочисленная арифметика), а label() возвращает 10% off для скидки 10 процентов.
  • FixedOff (создаётся с суммой): apply(total) возвращает общую сумму за вычетом суммы скидки, но не меньше 0, а label() возвращает 30 off для скидки в размере 30.
  • Cart: set_discount(new_discount) сохраняет стратегию, а checkout() складывает цены и возвращает Subtotal TOTAL, LABEL, pay PRICE, где PRICE — это результат, возвращаемый методом apply() стратегии.

Предоставленный код считывает цены в одной строке, количество последующих оформлений заказа и по одной стратегии в каждой строке: percent N, fixed N или none. Для цен 50 30 40 и стратегий percent 10, fixed 30, none вывод будет таким:

Subtotal 120, 10% off, pay 108
Subtotal 120, 30 off, pay 90
Subtotal 120, no discount, pay 120

Твой код должен находиться в файлах cart.gd, percent_off.gd и fixed_off.gd. В main.gd находится предоставленный код ввода/вывода, и его нельзя редактировать.

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

extends Node

func _ready():
	# Предоставленный код ввода/вывода: оставьте его без изменений
	var cart := Cart.new()
	for price in OS.read_string_from_stdin().split(" "):
		cart.add_item(int(price))
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var parts := OS.read_string_from_stdin().split(" ")
		if parts[0] == "percent":
			cart.set_discount(PercentOff.new(int(parts[1])))
		elif parts[0] == "fixed":
			cart.set_discount(FixedOff.new(int(parts[1])))
		else:
			cart.set_discount(Discount.new())
		print(cart.checkout())
quiz iconПроверьте себя

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

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

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