תבנית 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 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יסודות התכנות מונחה העצמים
קבצים חיצונייםמבוא ל-OOPמחלקות לעומת אובייקטיםמילת המפתח selfמתודותמאפייניםבנאי (_init)חזרה – מחשבון פשוט2מאפייני מחלקה
משתני מופע לעומת משתנים סטטייםפונקציות סטטיותקובעי מאפייניםמאחזרי מאפייניםפרטיים לפי מוסכמהסיכום – מנהל חשבון בנק5כימוס
איברים ציבוריים ופרטייםאימות באמצעות Settersהסתרת מידעמאפיינים לקריאה בלבדסיכום – מערכת לניהול רשומות תלמידיםתרגלו בעצמכם: קומפיילר GDScript אונליין