Паттерн «Стратегия»
Часть раздела Объектно-ориентированное программирование путешествия по 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 20Fighter никогда не проверяет, какой стиль у него есть. Новый стиль — это новый класс, а 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(), — это стратегия сортировки.
Задание
СреднеКорзина магазина может переключать стратегии скидок при оформлении заказа. 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())
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово selfМетодыСвойстваКонструктор (_init)Повторение: простой калькулятор4Полиморфизм
Переопределение методов: повторениеУтиная типизацияАбстрактные классы и методыПроектирование интерфейсовПовторение — калькулятор фигур7Сигналы
Что такое сигнал?Объявление и отправкаПодключение к сигналамСигналы с аргументамиПовторение — система здоровья10Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»2Свойства классов
Переменные экземпляра и классаСтатические функцииСеттеры свойствГеттеры свойствПриватность по соглашениюПовторение — менеджер банковского счёта5Инкапсуляция
Открытые и закрытые членыПроверка данных с помощью сеттеровСокрытие информацииСвойства только для чтенияПовторение — система учета студентов8Продвинутые концепции ООП
Композиция и наследованиеВнутренние классыУзлы как компонентыRefCounted и NodeПеречисления в классах11Проект: Управление библиотекой
Обзор проектаКлассы книг и пользователей3Наследование
Основы наследованияКлючевое слово superПереопределение методовЦепочки наследованияПроверка типов с помощью isПовторение — иерархия сотрудников6Специальные методы
Введение в виртуальные методыМетод _to_stringПользовательские итераторыПовторение: пользовательский список9Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн «Одиночка»Паттерн «Фабрика»Паттерн «Наблюдатель»Паттерн «Стратегия»Потренируйтесь самостоятельно: Онлайн-компилятор GDScript