תכנות מונחה עצמים (OOP) - יחסים בין מחלקות - Aggregation, Composition, Association

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

יחסים בין מחלקות: Aggregation, Composition, Association
 
מאת: ארז קלר

​הורדת דוגמאות הקוד

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

חלון התוכנית הוא אובייקט מורכב, חלון של תוכנית מורכב מאובייקט חלון (Form) המכיל בתוכו אובייקטים המכונים פקדים (Controls).
הפקדים גם הם אובייקטים מורכבים, גם הם מכילים מספר אובייקטים (כגון: אובייקט מחרוזת, אובייקט להגדרת מיקום הפקד, אובייקט להגדרת גודל הפקד, אובייקט גופן וכו').
לכל פקד אובייקט שכזה יש תפקיד מאוד מוגדר: קיים פקד לחצן המייצג את הספרה "0", פקד לחצן אחר מייצג את הספרה "1", פקד לחצן שלישי מייצג את הפעולה חילוק, פקד המציג את הקלט או את תוצאת החישוב וכו'.
עבור המשתמש החלון ופקדיו הם מכלול אחד בעל פונקציונאליות מוגדרת, החלון ופקדיו תלויים אחד בשני, חלון ללא פקדים חסר שימוש לחלוטין, ופקדים ללא החלון אינם יכולים לפעול ולהתקיים, ממש סימביוזה.
החלון, אם כן, הוא אובייקט מורכב המכיל אוסף אובייקטים (הפקדים),
לכל אובייקט באוסף הפקדים מוגדר תפקיד ייחודי, התפקוד המשותף של כולם יוצר את 
"המערכת" הנקראת תוכנית מחשב (Calculator בדוגמה).


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


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

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


1.Composition
דיאגרמת Composition

לסטודנט יש אוסף ציונים, אוסף הציונים של הסטודנט הוא אישי ותלוי בקיומו של אובייקט סטודנט, לא ניתן להעביר אובייקט המייצג אוסף ציונים לסטודנט אחר, דהיינו, מרגע הגדרתו אובייקט מהמחלקה GradeArray קשור בקשר חזק עם האובייקט מהמחלקה Student המכיל אותו.
מערכת היחסים בין האובייקט המוכל למחלקה המכילה נקראת Composition כאשר אורך החיים (Life Time) של האובייקטים המוכלים תלוי באורך חייו של האובייקט מהמחלקה המכילה אותו.
טווח הכרה (Scope) של האובייקט המוגדר תחת מערכת היחסים Composition הוא רק בתוך האובייקט המכיל.


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


3 - Association
דיאגרמת Association
מערכת יחסים שונה מהשניים הקודמות מגדירה שאובייקט אחד מכיר ונעזר באובייקט השני, לעיתים אומרים שאובייקט אחד מספק שירותים לאובייקט אחר או מספק שירותים המסתמכים על מידע או פעילות של אובייקט אחר.
אובייקט מהמחלקה Report נעזר באובייקט מהמחלקה Student על מנת להנפיק דוחו"ת.
 
הערה: במספר לא מבוטל של ספרים ומקורות ידע אחרים לא מודגשת החלוקה לסוגי הקשרים בין המחלקות, בד"כ מסתפקים ב- Aggregation או ב- Association , וזאת משום שהדגש הוא על הקשרים הפיזיים בין המחלקות/אובייקטים.
 
יחסי הכלה מזוהים על ידי "Has A":
לדוגמה : למחשב PC יש מסך,מקלדת, ספק כח, דיסק קשיח ...
                       למכונית יש מנוע, שלדה, הגה, גלגלים.
או על ידי "Part of Is":
לדוגמה: מסך הוא חלק ממערכת המכונה מחשב, מנוע הוא חלק ממכונית וכו'.
 
תרגיל דוגמה: ObjectRelationSample

המחלקה Car:
 1  : class Car 
 2  : { 
 3  :     private string licenseNum; 
 4  :     private string manufacturer; 
 5  :     private string manufactureModel; 
 6  :     private int yearModel; 
 7  :     public Car(string licence_num, string manufacturer, 
 8  :                      string manufacturer_model, int year) 
 9  :     { 
 10 :         this.licenseNum = licence_num; 
 11 :         this.manufacturer = manufacturer; 
 12 :         this.manufactureModel = manufacturer_model; 
 13 :         this.yearModel = year; 
 14 :     } 
 15 :     public void Print() 
 16 :     { 
 17 :         Console.WriteLine("Licence no:{0},Model:{1}-{2},Year:{3}", licenseNum, manufacturer, manufactureModel, yearModel); 
 18 :     } 
 19 : } 
אובייקט מהמחלקה Car מייצג את המכונית שיש לסטודנט, 
מכונית היא לא חובה, יתכן ויש לסטודנט מכונית ויתכן שלא.
אנו ניקח זאת בחשבון, ונתחשב גם בעובדה שסטודנט יכול להחליף מכונית.

המחלקה GradeArr:
 1  : class GradeArr 
 2  : { 
 3  :     private int[] gradeArray; 
 4  :     private int count; 
 5  :     public GradeArr() 
 6  :     { 
 7  :         gradeArray = new int[10]; 
 8  :     } 
 9  :     public void Add(int grade) 
 10 :     { 
 11 :         if (grade  >= 0 && grade < = 100) 
 12 :         { 
 13 :             if (gradeArray.Length == count) 
 14 :             { 
 15 :                 Array.Resize(ref gradeArray, count + 10); 
 16 :             } 
 17 :             gradeArray[count++] = grade; 
 18 :         } 
 19 :         else 
 20 :         { 
 21 :             Console.WriteLine("Error"); 
 22 :         } 
 23 :     } 
 24 :     public int CalcAvg() 
 25 :     { 
 26 :         int sum = 0; 
 27 :         foreach (int grade in gradeArray) 
 28 :             sum += grade; 
 29 :         return sum / count; 
 30 :     } 
 31 :     public void Print() 
 32 :     { 
 33 :         foreach (int grade in gradeArray) 
 34 :             Console.WriteLine("{0}, ", grade); 
 35 :     } 
 36 :     public int[] GradeArray 
 37 :     { 
 38 :         get 
 39 :         { 
 40 :             return gradeArray; 
 41 :         } 
 42 :     } 
 43 : } 
לכל סטודנט יש את אוסף הציונים הפרטי שלו, ציונים שהם רק שלו.
במהלך שנות לימודיו מתווספים עוד ועוד ציונים, אולם הם אינם יכולים להיות משותפים עם סטודנטים אחרים.

המחלקה Student:
 1  : class Student 
 2  : { 
 3  :     private int id; 
 4  :     private string lastName; 
 5  :     private string firstName; 
 6  :     private GradeArr gradesArr; 
 7  :     private Car car; 
 8  :     public Student(int id, string last_name, string first_name) 
 9  :     { 
 10 :         this.id = id; 
 11 :         this.lastName = last_name; 
 12 :         this.firstName = first_name; 
 13 :         this.gradesArr = new GradeArr(); 
 14 :     } 
 15 :     public void AddGrade(int grade) 
 16 :     { 
 17 :         gradesArr.Add(grade); 
 18 :     } 
 19 :     public int[] GetGradesList() 
 20 :     { 
 21 :         return gradesArr.GradeArray; 
 22 :     } 
 23 :     public int CalcAvg() 
 24 :     { 
 25 :         return gradesArr.CalcAvg(); 
 26 :     } 
 27 :     public int ID 
 28 :     { 
 29 :         get { return id; } 
 30 :     } 
 31 :     public string LastName 
 32 :     { 
 33 :         get { return lastName; } 
 34 :         set { lastName = value; } 
 35 :     } 
 36 :     public string FirstName 
 37 :     { 
 38 :         get { return firstName; } 
 39 :         set { firstName = value; } 
 40 :     } 
 41 :     public Car Car 
 42 :     { 
 43 :         get { return car; } 
 44 :         set { car = value; } 
 45 :     } 
 46 : } 
בשורה 6 מוגדר ייחוס למחלקה המייצגת את אוסף ציוניו של הסטודנט.
בשורה 7 מוגדר ייחוס למחלקת המכונית של האובייקט.
בשלב זה עדיין אין אנו יכולים לדעת מהי מהות היחסים בין האובייקטים המוכלים gradeArr ו-car לאובייקט האב מהטיפוס Student.
בשורות 15 , 19 ו-23 מוגדרות מתודות המטפלות באוסף הציונים של הסטודנט, בשום שלב הן אינן חושפות את הייחוס gradesArr מחוץ לכבולות המחלקה Student, לכן לא ניתן להעביר את הייחוס לאובייקט אחר.
מכיוון שהייחוס gradesArr אינה נחשפת לעולם החיצון שמחוץ למחלקה מדובר ביחסי Composition.
רמז נוסף שמרמז על יחסי Composition, אורך חיי האובייקט המוכל gradesArr כאורך חיי האובייקט המכיל מהטיפוס Student.

בשורה 41 מוגדר מאפיין באמצעותו ניתן לקבל ייחוס Car או לחשוף אותו לעולם, ניתן להבין מכך על יחסי Aggregation בין המכיל למוכל.
הקשר יכול להיות זמני וניתן להחלפה, כמו בדוגמה הבאה:
student1.Car = student2.Car;
student2.Car = null;
 
מה אפשר להבין משתי שורות הקוד הללו:
ש- student1 רכש את המכונית מ-student2 ולכן האובייקט החליף "בעלות".
וש- student2 נשאר ללא מכונית אחרת.


 
המחלקה Report:
 1  : class Reports 
 2  : { 
 3  :     public static void StudentReport(Student s) 
 4  :     { 
 5  :         StringBuilder builder = new StringBuilder(); 
 6  :         builder.AppendFormat("Student Report:{0} {1}", s.FirstName, s.LastName); 
 7  :         builder.AppendLine(); 
 8  :         builder.AppendLine("*************************"); 
 9  :         builder.Append("Student Details : "); 
 10 :         builder.AppendFormat("ID : {0}, Name : {1} {2}", s.ID, s.FirstName, s.LastName); 
 11 :         builder.AppendLine(); 
 12 :         builder.Append("Grades : "); 
 13 :         foreach (int grade in s.GetGradesList()) 
 14 :         { 
 15 :             builder.AppendFormat("{0}, ", grade); 
 16 :         } 
 17 :         builder.AppendLine(); 
 18 :         builder.AppendFormat("Average : {0} ", s.CalcAvg()); 
 19 :         System.IO.File.WriteAllText("report.txt", builder.ToString()); 
 20 :         Console.WriteLine(builder.ToString()); 
 21 :     } 
 22 : } 
הקשר בין המחלקה Student למחלקה Report הוא הארעי ביותר, המתודה (Reports.StudentReport(Student s מקבלת ייחוס מבמחלקה Student כפרמטר, אורך הקשר כאורך חיי במתודה, ברגע שהמתודה מסיימת את פעולתה מסתיים הקשר עד לפעם הבאה בו היא תופעל.
ולכן מדובר ביחסי Association.


Main:
 1  : class Program 
 2  : { 
 3  :     static void Main(string[] args) 
 4  :     { 
 5  :         Student s1 = new Student(123, "Israeli", "Rami"); 
 6  :         s1.AddGrade(100); 
 7  :         s1.AddGrade(80); 
 8  :         s1.AddGrade(90); 
 9  :         s1.AddGrade(75); 
 10 :         s1.AddGrade(85); 
 11 :         Console.WriteLine("Grades Avereage : {0}", s1.CalcAvg()); 
 12 :         s1.Car = new Car("12-345-67", "Mustang", "RoadRunner", 2007); 
 13 :         Reports.StudentReport(s1); 
 14 :         Student s2 = new Student(456, "Levi", "Yizhak"); 
 15 :         s2.Car = s1.Car; 
 16 :         s1.Car = new Car("23-789-62", "Susita", "Carmel", 2007); 
 17 :     } 
 18 : } 
אז מה ניתן להבין מ-Main? כל מה שהוסבר כבר למעלה (אבל תמיד טוב לסכם):
אובייקט מהמחלקה GradeArray מוסתר בתוך המחלקה Student, האחרונה אינה מאפשרת גישה אליו ממחלקות חיצוניות ולכן לא ניתן להעביר את הייחוס שלו לאובייקט אחר מהמחלקה Student .
לעומת זאת, המחלקה Student חושפת את האובייקט מהמחלקה Car, ולכן ניתן להחליפו באחר, להעבירו לאובייקט Student אחר, או לכל אובייקט אחר ממחלקה כלשהיא.
 
בניגוד ל- GradesArray ול- Car אשר מוגדרים כתכונות (Data Members ) של המחלקה Student , המחלקה Reports עושה שימוש באובייקט מהמחלקה Student כפרמטר, זהו קשר זמני שאורך חייו (Life Time)  כאורך חיי המתודה בלבד, זהו הקשר החלש ביותר.


פלט:
פלט תוכנית הדוגמה ObjectRelationSample
 


כל הזכויות שמורות למחבר ©