ב-Online
 
 
 
 
 
 
 
לפרק את הבייט 
איך בונים תוכנה - חלק ט' 

איך בונים תוכנה - חלק ט'

 
לפרק את הבייט |
 
עידו גנדל

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

 
 
 
 
 
 
 
 
 
השבוע ננסה לעשות את הבלתי אפשרי: להציג ולהדגים בכתבה אחת את הרעיונות שעומדים מאחורי תכנות מונחה עצמים (OOP – Object Oriented Programming). מדובר בתחום רחב ועמוק במיוחד, שמקדישים לו קורסים שלמים. לכן, כל מה שנראה כאן יהיה בהכרח הצצה חלקית בלבד – אבל עם קצת מזל, נוכל בכל זאת לפזר מעט את ערפל המסתורין. כיום, בזכות שפות מודרניות כמו ג'אווה או פייתון, המתכנתים נזרקים למים הקרים מיד בתחילת הדרך, אך מי שלמד שפות "קלאסיות" כמו C, פסקל או – ישמרנו האל – בייסיק, יכול להעריך את המהפך התפיסתי של תמ"ע (OOP) לעומת תכנות רגיל.

בתכנות הרגיל, יש הפרדה מובנת מאליה בין המשתנים, שהם נתונים סטטיים שמאוחסנים בזיכרון, לבין הפקודות שמשתמשות בהם. אם יש לי משתנה X ואני רוצה לבצע איתו משהו, אני אכתוב פונקציה בשם F שמקבלת את סוג המשתנה של X כפרמטר, ואשתמש בה כך: F(X). בתמ"ע, ה"עצם", או ה"אובייקט" (Object או Class ברוב שפות התכנות) אורז בחבילה אחת הן נתונים והן פקודות. כלפי חוץ הוא דומה לרשומה רגילה, אוסף של נתונים סטטיים, אך הוא יכול לכלול גם פונקציות משל עצמו. אם יש לי אובייקט בשם X, אני לא אכתוב פונקציה חיצונית כדי לתפעל אותו, אלא אשתמש בפונקציה הפנימית שמוגדרת עבור כל האובייקטים מאותו הסוג, כך: X.F.
 
הורשה בתכנות מונחה עצמים (עיבוד: עידו גנדל)
 הורשה בתכנות מונחה עצמים (עיבוד: עידו גנדל) 
 
 
עד כאן מדובר בעניין קוסמטי בלבד. השינוי האמיתי מתחיל בתכונת ההורשה (Inheritance). קחו לדוגמה את שלושת סוגי האלמנטים הגרפיים שמרכיבים את דיאגרמות הוורונוי בתוכנה שאנחנו כותבים. הנתונים שכל אחד מהם כולל מוצגים בטבלה שבאיור.
 

הגדרת אוביקט אב ואובייקטי-צאצא

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


TVoronoiCircle = class (TVoronoiPoint)
public
Radius : Integer;

function SaveToIniFile(var F : TMemIniFile; const Section : String) : Integer; override;
function LoadFromIniFile(var F : TMemIniFile; const Section : String) : Integer; override;
end;



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

תכנות מונחה עצמים: רב צורתי

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


function TVoronoiPoint.SaveToIniFile(var F : TMemIniFile; const Section : String) : Integer;
begin
F.WriteString(Section, INIKEY_ID, INI_POINT_ID);
F.WriteInteger(Section, INIKEY_X, X);
F.WriteInteger(Section, INIKEY_Y, Y);
F.WriteString(Section, INIKEY_COLOR, ColorToString(Color));
end;



זהו קוד חלקי, כמובן, וחסרים בו כל האמצעים לאיתור ומניעת תקלות. כל המשתנים באותיות רישיות הוגדרו מוקדם יותר בתוכנה ומסמלים את שמות המקטעים והמפתחות בקובץ ה-INI. במילים פשוטות, הפונקציה הזו רושמת בקובץ F את הזיהוי של הרשומה כנקודה, ולאחר מכן את ערכי ה-X, ה-Y והצבע שלה. כעת נעבור לקוד של פונקציית השמירה של העיגול:


function TVoronoiCircle.SaveToIniFile(var F : TMemIniFile; const Section : String) : Integer;
begin
inherited SaveToIniFile(F, Section);
F.WriteString(Section, INIKEY_ID, INI_CIRCLE_ID);
F.WriteInteger(Section, INIKEY_RADIUS, Radius);
end;

 
השורה הפנימית הראשונה, שמתחילה במילה Inherited, ממחזרת למעשה את הפונקציה הקודמת! אובייקט העיגול יורש אותה מאובייקט הנקודה, ופשוט קורא לפונקציה ההיא כדי לשמור את הנתונים המשותפים. אחרי השמירה של הנתונים המשותפים, הקוד "דורס" את הזיהוי כנקודה ומחליף אותו בזיהוי כעיגול, ואז שומר גם את נתון הרדיוס. המילה Inherited דרושה כדי להבהיר לקומפיילר שמדובר בפונקציה של האב ולא בפונקציה של הצאצא – הבהרה הכרחית משום שלשתיהן יש אותו שם בדיוק. וכאן מתגלה הפולימורפיזם במלוא הדרו: אובייקטים בעלי אב משותף יכולים להכיל פונקציות שנראות זהות כלפי חוץ, אבל מתנהגות באופן שונה לגמרי.

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

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


Function TVoronoiElementList.SaveToIniFile(var F : TMemIniFile);
var
j : Integer;

begin
F.Clear;
for j := 0 to Count - 1 do (GetItem(j) as TVoronoiPoint).SaveToIniFile(F, IntToStr(j));
end;

 

רשימת אלמנטים גרפיים ללא צורך בכתיבה מחדש

סוג האובייקט TVoronoiElementList, לו שייכת פונקציה זו, הוא בעצמו צאצא של הסוג TObjectList המובנה בדלפי, ואשר יודע לנהל רשימה מקושרת. ההורשה ממנו מאפשרת ליצור רשימה מותאמת של אלמנטים גרפיים, בלי להסתבך בכתיבה מחדש של כל הקוד הדרוש לניהול רשימה מקושרת שכזו. האובייקט החדש מוסיף רק את הפונקציות הדרושות לו, כגון פונקציה זו לשמירה לקובץ INI. הפונקציה GetItem היא פונקציה מובנית באובייקט האב אשר מחזירה מצביע לאובייקט מהרשימה, והקוד " as TVoronoiPoint" מדריך את הקומפיילר להתייחס למצביע שהתקבל כאל מצביע מסוג נקודה (או כל אחד מהצאצאים שלו). קוד זה שומר כל אלמנט גרפי בעזרת הפונקציה שמוגדרת עבורו. אם לא הוגדרה פונקציה ספציפית, הוא ישתמש בפונקציה שהוגדרה אצל אובייקט האב.

עוד יתרון אחד חשוב של התע"מ הוא הכימוס (Encapsulation), היכולת "לסגור" קוד ומשתנים של האובייקט מפני גישה חיצונית. כאשר אתם כותבים את כל התוכנה בעצמכם אין לזה הרבה משמעות, אך אם אתם עובדים בצוות או מפיצים אובייקטים לשימוש של אחרים, הדבר הנכון לעשות הוא ליצור חבילה סגורה ובדוקה, שהאחרים יראו רק את הממשק שלה. מה שקורה בפנים לא צריך לעניין אותם, ואם זה עובד עדיף גם שהם לא ייגעו בזה. בעתיד, במקרה הצורך, תוכלו לשנות את כל הקוד הפנימי הזה ואף אחד לא יידע, כל עוד הממשק (שמות הפונקציות והמשתנים הגלויים) לא משתנה. זו המודולריות בהתגלמותה.
 
 
 
@@@@@@@@@@@@@@@@@@@ ilan @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
@@@@@@@@@@@@@@@@@@@ ilan @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
 
 
תגובות
הוסף תגובה0 תגובות
הוספת תגובה
מאת
 
נושא
 
תוכן
 
 
 
 
תודה! תגובתך התקבלה.
התגובה תתפרסם בכפוף לתנאי האתר.
 
 
 
 
 

כל הזכויות שמורות 2011 © נענע 10 בע"מ
 
 
 
 
כל הזכויות שמורות © Nana10 בע"מ
Video powered by