Menu
CoddyTech

תבנית Strategy

חלק מהיחידה תכנות מונחה עצמים במסלול ה-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 אונליין