אבסרטקציה פרקטית
אבסטרקציה יוצרת גורם המממש רעיון.
כל מימוש נוסף שנגזר מהגורם יכול להוסיף התנהגות נוספת.
או לחילופין - אבסטרקציה בפרקטיקה מאגדת רעיונות שונים בתוך רעיון אחד.
מערכת לחימה במשחק תפקידים
לעצב מחלקות במשחקי תפקידים נותן לנו אפשרות להיות יצירתי במימוש רעיונות.
במשחק התפקידים שאנחנו בונים יש לדמויות שלנו ביגוד שמקנה להם נקודות הגנה והתקפה.
חרב - 20 נקודות התקפה
שריון - 15 נקודות הגנה
מגן - 10 נקודות הגנה
נתחיל ממערכת פשוטה,
לכל דמות יש נקודות התקפה ונקודות הגנה שמחושב מסכום החפצים שלהם.
נתחיל מכמה מחלקות מאוד פשוטות:
1 | from dataclasses import dataclass |
וכעת הפונקציה שתחשב את כמות הנזק:
1 | def TotalDamage(first: EquipableItem, second: EquipableItem): |
בשביל לחשב את הנזק שדמות אחת מבצעת על השנייה נוכל לחשב בעזרת הלבוש שלהם.
הבעיה המהותית היא שהנזק הוא לינארי וכל נזק שנוסיף ייגרום לתחרותיות קשה.
אפילו במספרים קטנים זה יהיה בעייתי כי אין לדמות החלשה סיכוי:
אני לא אתמקד בעיצוב המערכת של המשחק, אחת מדרכים לשבור את הגדילה הלינארית היא בעזרת הוספת אלמנטים נוספים למשחק.
כעת נרצה להוסיף אלמנט הגנתי למגנים - לכל מגן יהיה סיכוי כלשהו להגן לחלוטין מהנזק.
במשחקי תפקידים האפקט הזה נקרא “חסימה”.
המימוש הנאיבי
ללא אבסטרקציה אנו ניישם מיד את הרעיון העומד מאחורי החסימה:
1 |
|
ונוסיף את קוד הבדיקה:
1 | if isinstance(first.WeaponOne, Shield) and random.randint(0, 100) < first.WeaponOne.BlockChance: |
הרעיון אכן מומש, חוץ מזה ש-randint
קשה לבדיקה וגם כי הכנסו קוד מאוד ספציפי למגנים לפונקציית ההתקפה שלנו.
הצורה הנאיבית מבחינת תכנות מונחה אובייקטים לוקח כל קונספט כמובן מאליו וישירות הופך את הרעיון למימוש:
הבעיה הפרקטית הראשונה במימוש ישיר הוא שנצטרך להוסיף לאותו מקום בקוד עוד ועוד לוגיקה כך שפונקצית חישוב הנזק תגדל ותגדל.
כדי למגר את הישירות הזו נוכל להשתמש באסטרקציה כי ליצור גורם מקשר בין רעיון החסימה למימוש מכניקת המשחק.
המימוש האבסטרקטי
שלב ראשון - ליצור אפקט לנזק
נוציא אבסטרקציה מרעיון החסימה.
לחסום נזק זה בעצם להוריד את הנזק ל-0.
הורדת הנזק זה בעצם שינוי הנזק שנעשה.
אם משנים את הנזק נוכל לעשות אפקטים שגם מעלים את הנזק או מורידים אותו בהתאם לההתנהגות שאנחנו רוצים.
נכתוב את זה בקוד:
1 |
|
סתם לשעשוע, אם נרצה באמת אפקט שמחזק את החפץ נוכל לממש גם כן:
1 |
|
שלב שני -נוסיף אפקטים לחפצים
1 |
|
שלב שלישי - עדכון פונקציית הנזק בהתחשבות החפצים
1 | def TotalDamage(first: EquipableItem, second: EquipableItem): |
כעת הפונקציה מתחשבת באבסטרקציה במקום בחוק מוגדר מראש:
הכלה מול הורשה
נקודה נוספת לתשומת לבכם -
יש כאן שימושים בשני טכניקות, גם ההורשה וגם ההכלה.
ההורשה ממומשת בכך שכל אפקט יורש את מחלקת הבסיס - אם כי פה זה מוגדר יותר כמו ממשק מאשר מחלקה משלה.
ההוכלה אומרת שכל חפץ מכיל אפקט כזה, ולא הוא בעצמו אפקט כזה.
שימו לב יש לזה משקל רב - להאם רכיב א’ הוא סוג של רכיב ב’, או רכיב א’ מכיל רכיב’ ב’.
למשל:
- סוזוקי הוא רכב
- לרכב יש 4 גלגלים
Visitor pattern
בהעברת item
ו-damage
אנו בונים את רכיב האפקט כvisitor
.
זאת אומרת שהוא אינו יודע פרט מעבר למה שהוא מקבל בפונקציה ובמקום שהוא יישתלט על פונקציה חישוב הנזק, הוא חלק ממנה ויודע רק את המעט.
זהו לא visitor
חזק כל כך אבל פה הוא נמצא בשימוש גם כן.
סיכום
כמובן שמימוש מערכות משחקים זה עניין מורכב ותלוי במשחק ובאיזה סוג של כיף רוצים.
ככה גם בקוד שלנו לעיתים נרצה להוסיף רמת אבסטרקציה נוספת על מנת שנוכל לאגד כמה רעיונות תחתיו.
כאן במקום לממש באופן ישיר אפקט חסימה, נתנו למילה “אפקט” מימוש משלו בתור מחלקה.
יצירת מחלקות עבור קונספטים זה חלק מתכנות מונחה עצמים - כאשר עושים את זה טוב נוכל ליצור מערכת מודולרית שאינה תלויה ברכביה,
המימושים יהיו במחלקות שונות ככה שהקוד מבוזר,
ובמידה וצריך לשנות משהו אנו עומדים במנגנון האחריות הבודדת עבור כל רכיב במערכת.
אז לסיכום:
אבסטרקציה תורמת למערכת שלנו מודולריות
אבסטרקציה עוזרת לממש קונספטים באופן עקיף
אבסטרקציה מסתירה את המימוש מהקוד
תודה על הקריאה!