Strategy Pattern
Part of the Object Oriented Programming section of Coddy's GDScript journey. Lesson 49 of 65.
The Strategy pattern puts each version of an algorithm in its own object, so you can swap the algorithm while the program runs. The object that uses it, the context, calls one method and does not care which version it holds.
Here are two attack strategies, a parent class and a subclass with the same method 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 * 2The context stores a strategy and hands the calculation to it:
# 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 never checks which style it holds. A new style is a new class, and fighter.gd stays as it is; an if style == "heavy" chain inside attack() would grow with every style.
When a strategy is one small function with no data of its own, a Callable is enough. The context stores the function and runs it with 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
55Use classes when a strategy has its own settings or several methods, and Callables for one short calculation. You have used this idea before: the comparison function you pass to sort_custom() is a sorting strategy.
Challenge
MediumA shop cart can switch discount strategies at checkout. Discount in discount.gd is the base strategy: no discount. Write two more and the cart:
PercentOff(built with a percent):apply(total)returnstotal - total * percent / 100(integer math), andlabel()returns10% offfor 10 percent.FixedOff(built with an amount):apply(total)returns the total minus the amount, but never below0, andlabel()returns30 offfor an amount of 30.Cart:set_discount(new_discount)stores the strategy, andcheckout()adds up the prices and returnsSubtotal TOTAL, LABEL, pay PRICE, where PRICE is what the strategy'sapply()returns.
The supplied code reads the prices on one line, how many checkouts follow, and one strategy per line: percent N, fixed N or none. For the prices 50 30 40 and the strategies percent 10, fixed 30, none the output is:
Subtotal 120, 10% off, pay 108
Subtotal 120, 30 off, pay 90
Subtotal 120, no discount, pay 120Your code goes in cart.gd, percent_off.gd and fixed_off.gd. main.gd holds the supplied input/output code and cannot be edited.
Try it yourself
extends Node
func _ready():
# Supplied input/output code: keep it as it is
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())
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesIntroduction to OOPClasses vs ObjectsThe self KeywordMethodsPropertiesConstructor (_init)Recap - Simple Calculator4Polymorphism
Method Overriding RevisitedDuck TypingAbstract Classes and MethodsInterface DesignRecap - Shape Calculator2Class Properties
Instance vs Static VariablesStatic FunctionsProperty SettersProperty GettersPrivate by ConventionRecap - Bank Account Manager5Encapsulation
Public and Private MembersValidation with SettersInformation HidingRead-Only PropertiesRecap - Student Records System8Advanced OOP Concepts
Composition vs InheritanceInner ClassesNodes as ComponentsRefCounted vs NodeEnums in Classes11Project: Library Management
Project OverviewBook and User Classes3Inheritance
Basic InheritanceThe super KeywordMethod OverridingInheritance ChainsType Checks with isRecap - Employee Hierarchy6Special Methods
Virtual Methods IntroductionThe _to_string MethodCustom IteratorsRecap - Custom List9Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy PatternPractice on your own: Online GDScript compiler