בהמשך לשבוע הגנרי הטוב,
נניח שיש לנו ממשק של משהו שמזכיר מספר
|
|
זה בעצם איזשהו טיפוס שיש לו פונקציה Add שמקבלת איבר ומחזירה את הסכום של הinstance עם האיבר שהתקבל.
איזה דברים יכולים לממש את זה?
הרבה דברים מתמטיים:
בעצם כל דבר שסגור לחיבור:
מחלקה של מספרים שלמים שתכתבו, מחלקה של מספרים ממשיים/רציונליים/מרוכבים שתממשו, מחלקה של מטריצות מסדר latex 5 \times 6 $ שתממשו, מחלקה של וקטורים ממימד 3 שתממשו, ועוד ועוד.
הכל נראה טוב ויפה, עד שנגיע לבעיה הבאה:
יש לנו שתי מחלקות
|
|
כעת נוכל לעשות משהו כזה:
|
|
אני אסביר את הבעיה:
באמת אפשר לחבר מטריצות (אחת עם השנייה), ואפשר גם לחבר מספרים רציונליים (אחד עם השני), אבל אי אפשר לחבר מספר רציונלי עם מטריצה בגודל $ 4 \times 4 $!
מאחר וכל המחלקות שלנו מממשות IAddable, אפשר לחבר אותן אחת עם השנייה.
בפועל לרוב נקבל שגיאה בזמן ריצה מאחר ובפונקציה Add המממושת בRationalNumber בוודאי תהיה הסבה של other (המשתנה שאנחנו מקבלים בפונקציה) לRationalNumber.
פתרון:
שלב 1: נהפוך את הממשק לגנרי:
|
|
כעת המימושים יראו כך:
|
|
והשורה השטנית
|
|
כבר לא מתקמפלת!
The best overloaded method match for ‘RationalNumber.Add(RationalNumber)’ has some invalid arguments
Argument ‘1’: cannot convert from ‘Matrix4x4’ to ‘RationalNumber’
הכל טוב ויפה, לא?
לא.
למה לא?
כי נוכל למשל לעשות שטויות מהסוג הזה:
|
|
מה הבעיה? היינו מצפים שDumbAddable יקבל בפונקציית הAdd שלו DumbAddable ויחזיר DumbAddable, כמו שהיה בכל הדוגמאות הקודמות. בעצם הפסדנו את היתרון של "הסגירות" שהיה לנו בIAddable הלא הגנרי.
אתם יכולים להגיד שזו בעיה של המתכנת, אם הוא מחליט לעשות שטויות ולא לממש את הממשק כמו שצריך.
זה לא נכון, משתי הסיבות הבאות:
- תשתית אמורה להגן כמה שניתן מהמתכנת מלעשות דברים לא נכונים.
- זה פוגע גם בתשתית. נניח שנכתוב פונקציה כזאת:
|
|
פונקציה לגיטימית שמחברת הרבה IAddable<T>.
זה לא יתקמפל לנו:
The best overloaded method match for ‘IAddable.Add(T)’ has some invalid arguments
Argument ‘1’: cannot convert from ‘IAddable’ to ‘T’
שלב ב’ של הפתרון:
נשנה את הממשק כך:
|
|
עכשיו אתם אמורים לחשוב לעצמכם, רגע, זה לא רקורסיבי????
התשובה הפשוטה היא שזה לא.
כדי להבין את זה, אני אוהב לחשוב על הדוגמה היותר פשוטה שאנחנו מכירים מלימודי פוינטרים בC:
כאשר מממשים רשימה כותבים בד"כ משהו כזה
|
|
ואז עולה שאלה דומה, איך זה לא רקורסיבי. התשובה שם היא שlistNode* זה רק פוינטר ולכן אין בעיה.
נחזור לשלב ב’ של הפתרון. כעת לא תתקמפל המחלקה DumbAddable:
The type ‘int’ cannot be used as type parameter ‘T’ in the generic type or method ‘IAddable’. There is no boxing conversion from ‘int’ to ‘IAddable’
באשר לפונקציה הנחמדה שכתבנו, נצטרך לעשות כמה תיקונים, וכעת היא תתקמפל!
|
|
שימו לב שעכשיו המתודה גם נראית הרבה יותר טוב, אין בה בשום מקום IAddable<T>, חוץ מבConstraint.
סופ"ש גנרי מצוין