עבור לתוכן

OOP, מחלקה לכל הדבר. האם זה הגישה הנכונה?

Featured Replies

פורסם

יש האומרים שכל דבר צריך להיות אובייקט/מחלקה. ניקח פנס, הדבר הפרימיטיבי שחשבתי עליו. הוא יכול להיות בשני מצבים כבוי ודלוק. נראה דיי טיפשי ליצור מחלקה של 6 שורות בשביל פנס. אבל פנס זה אובייקט כמו כל אובייקט.

ניקח דוגמא אחרת חודשי שנה. בוא ניקח פרוטוטיפ של הפונקציה הבאה:

void printDaysInMonth(int month)

גישה נכונה,הרבה עושים ככה, אבל רק מה, הגבולות של INT הם מעבר ל 12. כמו כן אין טיפוס נתונים אשר הגבולות שלו הם 1 עד 12.

אפשר לייעל להציע למשתמש ENUM לדוגמא משהוא כזה:

enum{

Januray=1,

February,

March,

April,

....

};

זה אומנם מקל על ההשתמש אבל לא מונע ממנו להכניס חודש מספר 13 או 60.

יש שיגידו: "אבל אפשר בתוך הפונקציה לבדוק שאם החודש לא בתחום 1-12 להדפיס טעות". נכון אבל יש בעיה קטנה, לא יודע איך לתאר אותה אבל פונקציה צריכה לבצע את מה שהיא צריכה לבצע ולא יותר. printDaysInMonth = הדפס ימים בחודש, היא לא אמורה לדאוג לקלט אלה רק להדפיק ימים בחודש.

לא מזמן ראיתי את הפתרון הבא:

void printDaysInMonth(const Month& m)

כאשר Month היא מחלקה (אפשר לממש אותה בכמה דרכים אני לא אכנס לזה כרגע, תקחו בחשבון שהיא דואגת שחודש יהיה בתחום 1 עד 12).

האם זה שיטה יותר נכונה מאשר נגיד לבדוק את בקלט בפונקציה void printDaysInMonth(int month?

כמו כן ראיתי דוגמאות כאלה עם מעלות, רדיאנים וכדומה.

האם זה באמת יעיל ליצור לכל אובייקט מחלקה? "חודש" מה כבר הוא יכול לבצע? זה כולא מחלקה שמחזיקה את שם החודש (שוב לא ניכנס למימוש שלה), היא לא נותנת אפשרות של ביצוע פעולות כלשהן על החודש כי איזה פעולות אפשר כבר לבצע על חודש?

מצטער שחפרתי הרבה, מקווה שהבנתם את מהות השאלה.

תודה רבה רבה מראש! ושנה חדשה לכולם :)

פורסם

בגדול הגישה הזו נועדה למזער למינימום האפשרי את הטעויות שעלולות להתעורר בשימוש העצם, ליצור קוד בטוח, וכן מקלה מאוד על וורסטיליות ומודולריות של שימוש עתידי בו. אם אתה מגדיר מחלקה לכל עצם, אתה בעצם מגדיר את אופי יצירתו (כלומר דואג שהעצם בהתהוותו חוקי - מתקיים אך ורק תחת מגבלות תכונות העצם עצמו), וכן בכל פעולה המתבצעת על העצם דואג בפונקציות המחלקה לשמור על מבנה העצם תקין, וכן להשתמש בנתוניו כדרוש.

למשל, עבור דוגמת חודש אצלך: אם תבנה מחלקת MONTH תוכל לדאוג שבעת יצירת החודש, הוא לא יוכל להיות קטן מ-1 או גדול מ-12. כמו כן אתה יכול לתמוך בפעולות רבות נוספות: לדוגמא, אם אתה רוצה לחבר חודשים (אופרטור +) עליך להגדיר חיבור מודולו 12. כמו כן, אם נניח התעורר אצלך צורך מאוחר יותר למשימות נוספות לאוביקט חודש, תוכל ליצור אוביקט חדש היורש מאותו חודש, ומשתמש בכל אותן תכונות ופעולות שהוגדרו היטב, ונבדקו בזמן בנייתן, וכן מוסיף תכונות או פעולות נוספות משל עצמו.

למשל: הצורך החדש הוא חודש, המכיל רשימת ימים (בין 1 ל-31 לדוגמא) כך שלכל יום יש רשימת ארועים. חודש זה יירש מהחודש שהגדרת קודם, וכן יוסיף את התכונות והפעולות הדרושות, למשל לחשב כמה אירועים התרחשו באותו חודש, וכו'.

באופן כללי, שיטה זו אמנם עשוייה להיראות טרחנית מעט בתחילה, שכן נראה די מוזר שלכל "שטות" יוצרים מחלקה, אבל עם ניסיון ושימוש חוזר בקוד מגלים שההשקעה הראשונית בהגדרה טובה של אוביקטים ופעולות, הקוד הופך דימני מאוד ומקל על מימוש משימות עתידיות.

פורסם

לא צריך להגדיר מחלקה עבור חודש. enum הוא הפתרון.

תגדיר:

enum Month {
January = 1,
February,
March,
// the rest
};

ואז להגדיר את הפונקציה

void printDaysInMonth(Month month)

הפתרון הזה לא fool proof, כיוון שב-++C ה-enumים הם עדיין int (המשתמש עדיין יוכל להעביר את המספר 13 לפונקציה).

שפות אחרות, כגון #C, לא יתנו למשתמש להעביר מספר לא חוקי ב-enum.

בתכנות OOP נכון, כל פונקציה פומבית צריכה לוודא שהקלט שלה תקין.

פורסם
  • מחבר

תודה רבה על התגובות חברה, אשמח לשמוע עוד תגובות.

פורסם

באופן כללי, אין ממש חוקי "הכל או לא כלום" בתכנות. כל דבר שאתה עושה נבחן לפי ההשקעה לעומת התועלת, המחיר לעומת הסיכוי לרווח.

בסופו של דבר תכנות מונחה עצמים היא עוד טכניקה למידול תוכנה ומערכות, אשר מתאימה למגוון רחב של דברים, אבל לא להכל. גם כשכתיבת מחלקות היא מתאימה, יש לה מחיר, ולא תמיד צריך לשלם אותו.

לשאלה הספציפית: תלוי מה סוג התוכנה שאתה עושה, אבל לרוב סקלרים פשוטים כמו חודש, יום, שעה, משקל, זווית, להגדיר מחלקה זה לא מועיל במיוחד. עבור טיפוסים עם מספר ערכים ספור ודיסקרטי (חודש, יום...) אפשר להגדיר Enumerated types כמו שאמר שניצל. לאחרים הרבה פעמים מספיק או אפילו עדיף להגדיר כמספר, עם בדיקות תקינות או הצהרות של design by contract כמו שיש ב-eiffel.

כמובן שיש תוכנות שבהן דברים כמו חודש, יום ושעה הם חשובים במיוחד, אולי חלק מרכזי בתוכנה (לוח שנה, לדוגמא), ואז יש להניח ששם דווקא כן יש אובייקטים מורכבים לנושא שבתוכנה אחרת הוא מאוד פשוט.

עוד נקודה למחשבה בשבילך:

בשפות עם strong static typing ניתן לדמיין (ואף לממש) טיפוס נפרד למהירות (לדוגמא) ולתאוצה, וזאת למרות ששניהם בעצם מספרים. כך שאם המתכנת מתבלבל ביניהם זו תהיה שגיאת קומפילציה, ואז נמנעעם סוגים מסויימים של באגים. עוד שימוש אפשרי זה להבדיל בין אורך במטרים ואורך ברגליים, לדוגמא. גם כאן יש מחיר.

פורסם
  • מחבר

Zelig

הבנתי אותך תודה רבה.

פורסם

העוצמה של תכנות מונחה עצמים באה בעיקר ביכולת ההורשה

אז אם ניקח את הדוגמא שנתת על הפנס

הפנס הכי פרמטיבי הוא קרוב לוודאי חתיכת צינור פלסטיק המכיל בטריות עם נורית בקצה ויש לו פעולת הדלקה וכיבוי

אבל ממנו אתה יכול לרשת "פנסים" יותר מתקדמים:

יכול להיות שהחומר ממנו עשוי הגוף חשוב

פנס שמולבש על קסדה

יש כאלה שיש להם אפשרות לשנות עוצמת התאורה

יש כאלה עם אפשרות למקד או לפזר את אלומת האור

ישנם פנסים שעולים מאות דולרים המשמשים צוללנים

בקיצור רוצה לומר שכמעט מכל דבר שנראה לך מאוד פשוט ומיותר לכאורה לכתוב עבורו קלאס בתכנית ,

אפשר לרשת ולהוסיף להם עוד ועוד תכונות ושיטות כך שתקבל קלאסים יותר מורכבים.

פורסם
  • מחבר

NJorl

אני מסכים אבל יש דברים שלא ניתן לרשת מהם כמו "מעלות", "רדיאנים" אי אפשר לפחות לא היום. אין אזשהוא רדיאן שהוא מעבר לרדיאן הפשוט שקיים כרגע.

השאלה האם יש צורך ליצור נגיד מחלקות לאותם מעלות,רדיאנים. כי ראיתי בכמה תוכנות (מנועי משחק) בעיקר בקטע שקשור למטמטיקה שהם כן יוצרים מחלקה לדברים האלה.

פורסם

הסיבה שמחלקות אלה מוגדרות כ-FINAL ולכן אי אפשר לרשת מהן היא מכיוון שהן מייצגות מושג מתימטי קבוע שלא רוצים שתשנה אותו.

אתה תמיד יכול להגדיר מחלקה שתשתמש במופע של אחת המחלקות האלה ותוסיף פונקציונליות משלך בלי לרשת מהן.

פורסם

MasterDK יש לי בשבילך עוד סיבה למה הערכים הכי פרימיטיביים גם כן כדאי שיהיו מוגדרים כ-קלאסים

הנושא הוא : boxing ו- unboxing

ב- שפות .NET לדוגמא, הטיפוס int שהוא value type הוא למעשה קלאס שיורש מהקלאס object

לפעמים יש מצבים שאתה צריך לטפל באובייקטים שאינך יודע מראש מה הטיפוס שלהם

דוגמא לכך זה כל אותם הקלאסים שהם אוספים Collections שהם קלאסים שמנהלים מבני נתונים של אובייקטים אחרים:

רשימה מקושרת, תור, מחסנית וכו'

תעיין בקלאסים האלה בספריית System.Collections

ותראה שלרוב יהיה להם איזו שהיא מתודה כמו Add , Enqeue או Push כדי להוסיף איבר לרשימה

המתודות הללו מקבלות כפרטמר טיפוס מסוג object זאת אומרת אפשר לאחסן ברשימה הזו כל מה שרק תרצה

כיוון שגם טיפוס מסוג int הוא קלאס שיורש את object אפשר גם int להוסיף לרשימה

כך אתה יכול לדוגמא לנהל רשימה של מספרים כשאתה לא יודע מראש מה גודל הרשימה ומערך הוא לא פתרון טוב במקרה כזה

בזמן הוספת איבר מסוג int לרשימה כזו מתבצעת למעשה פעולת boxing כלומר "עטיפת" ה- int כ-אובייקט מסוג object

כאשר אתה עובר על הרשימה למשל כדי להדפיס את המספרים מתבצעת פעולת unboxing כלומר הסרת העטיפה של ה- object וגישה ל- int עצמו.

פורסם
  • מחבר

הממ לא חשבתי על זה בדרך הזאתי (כי כנראה שפשוט לא תיכנתתי בשפת JAVA/C#) אבל תודה שהעלת את הנקודה הזאתי באמת נשמע שימושי.

פורסם

אני חושב שזו דוגמא רעה. JAVA ו-C# עברו מ-collections שמכילים היררכיות מסובכות מטיפוס כללי, ל-generic collections שיכולים להכיל טיפוסים מסוג ידוע. זה נעשה בדיוק כדי להמנע מאוספים של עצמים מסוג לא ידוע. זה גרר בעיות ביצועים רבות וכן באגים רבים.

ב-JAVA גם ה-boxing הוא לא ממש אוטמטי, עד כמה שאני זוכר, ומראש אתה צריך להגדיר Integer במקום int ולהמיר בעצמך לפעמים.

בעולם האמיתי לא כל דבר מצדיק כתיבת מחלקה מסביבו, וכמו שציינתי, לא כל דבר מתאים לכתיבת מחלקה מסביבו. הכי חשוב זה להתמקד במשימה שלשמה אתה מתכנת. ספרית עיבוד תמונה לא צריכה מחלקה שלמה מסוג FileName. בד"כ מחרוזת זה מספיק. היא כן צריכה מחלקה כמו Region או Image.

פורסם

אני חושב שזו דוגמא רעה. JAVA ו-C# עברו מ-collections שמכילים היררכיות מסובכות מטיפוס כללי, ל-generic collections שיכולים להכיל טיפוסים מסוג ידוע. זה נעשה בדיוק כדי להמנע מאוספים של עצמים מסוג לא ידוע. זה גרר בעיות ביצועים רבות וכן באגים רבים.

למה אתה מתכוון "עברו מ-" ?

הם לא "עברו מ-"

יש גם וגם

יש אוספים שעובדים על generic כפי שציינת המקבלים תבנית של טיפוס <T> בזמן הקונסטרקטור ואז כל איבר שאתה מכניס לרשימה יכול להיות רק מטיפוס T

אבל תמיד תהיה גם דרישה לשימוש באוספים שמקבלים אובייקט מקלאס פרמיטיבי כדי שתוכל להכניס לרשימה טיפוסים שונים , גם מצבים כאלה לפעמים צריך

אין פה עניין של הזניחו שיטה אחת והוסיפו שיטה אחרת במקומה

שני סוגי האוספים קיימים אף אחת לא מחליפה את השניה

הדוגמא שנתתי , מצויינת

עצבנת אותי

פורסם

אל תצעבן, זה כולה פורום.

באופן כללי ההמלצה הרווחת שאני רואה היא להשתמש ב-generics, שהם יותר מהירים ויותר בטוחים מהגרסאות הקודמות. תמיד אפשר ליצור אוסף גנרי של object (או המקבילה בשפה). אבל בד"כ אוסף שיש בו int, NuclearSubmarine ו-Apple זה לא רעיון טוב, ומסמן על היררכית אובייקטים לא מתוכננת היטב.

הניחוש שלי (וזה רק ניחוש, ורושם שקיבלתי) הוא שאם מתכנני C# היו יכולים להתחיל מחדש את השפה, הם היו נפטרים מה-collections הישנים (כיוון שאתה יכול לקבל את אותה תוצאה עם החדשים).

בכל מקרה boxing ו-unboxing זו בדיוק הסיבה למה לא כל דבר חייב להיות אובייקט. אם השפה מבצעת boxing באופן אוטומטי, זה בדיוק סיבה לא להפוך מספרים פשוטים לאובייקטים - השפה תעשה את זה בעצמה אם אתה חייב להכניס מספר לאוסף. אם אתה צריך אובייקט של int מסיבות אחרות, boxing זה לא מה שיציל אותך.

סיבות טובות: בדיקות חוקיות, מעקב גישה, וכו'.

ארכיון

דיון זה הועבר לארכיון ולא ניתן להוסיף בו תגובות חדשות.

דיונים חדשים