الفئات والكائنات
(برمجة موجهة كائنات)
12.1 مقدمة – מבוא
البرمجة موجهة الكائنات هي البرمجة الأساسية التي بُنيت عليها لغة السي شارب، وهذا عبارة عن توجه في البرمجة. هذا التوجه يحاكي الواقع الحقيقي والعالم الحقيقي الذي نعيش فيه حيث تتواجد الكثير من الأشياء (الكائنات) التي تتفاعل فيما بينها أو لوحدها ويحدث عن ذلك أحداث ونتائج، مثلا الكائن انسان يتفاعل مع الكائن آلة صنع القهوة ومع الكائن كبسولات القهوة وينتج عن ذلك فنجان الاسبريسو مثلا. لذلك يتم بناء برامج السي شارب بحيث تحتوي على العديد من الكائنات (עצמים/objects) التي تعمل معاً وتتفاعل فيما بينها وفقا لعمليات وبرنامج رئيسي يحدد ذلك، الكائنات والعمليات والبرنامج الرئيسي يتم تحديدها حسب هدف المبرمج وبالتالي نحصل على محاكاة للعالم الحقيقي ونستطيع بذلك إدارة مشاريع ومتطلبات توصلنا الى هدف محدد.
البرمجة موجهة الكائنات (OOP) تُعتبر برمجة حديثة متطورة ذات نجاعة أعلى ومرونة، فيها إمكانيات أكثر وذات وضوح أكثر ومن أهم ميزاتها أنها برمجة تمكننا من الارتكاز على العمل على توزيع المهام وتقسيمه ثم الدمج (שילוב) فهي برمجة ذات قدرة على العمل المُدمج.
المصطلحات الأساسية في هذا التوجه من البرمجة هي:
- الفئة (מחלקה/class).
- الكائن (עצם/object).
- العملية البنائية (بنّاء) (פעולה בונה או בנאי/ constructor).
- عمليات الوصول (פעולה גישה/ setters and getters).
- الصفات والثوابت (תכונות/ attributes).
- العمليات (الطرق) (פעולות או שיטות / methods).
- المُشغل نقطة . (operator dot).
- المُوجه this (המפנה this).
- مُحدّد الوصول (הרשאות גישה/access modifier).
- التحميل الزائد (העמסה/Overload). (*)
- تعليب أو تغليف (כימוס/ Encapsulation).
12.2 الفئة والكائن – המחלקה והעצם
سنبدأ بالتعرف على الفئات (מחלקות/classes) والكائنات (עצמים/objects)
12.2.1 الفئة (מחלקה/class) – המחלקה
يمكن اعتبار الفئة على أنها مجموعة (أو إطار عام) وهي تعريف عام لإنشاء كائنات تابعة لهذا الإطار.
امثلة:
- الفئة يمكن أن تكون الحيوانات عندها كل كائن تابع لهذه الفئة هو حيوان (كائن).
- الفئة يمكن أن تكون السيارات عندها كل كائن تابع لهذه الفئة هو سيارة (كائن).
- الفئة يمكن أن تكون المُعلمين عندها كل كائن تابع لهذه الفئة هو مُعلم (كائن).
- الفئة يمكن أن تكون الطلاب عندها كل كائن تابع لهذه الفئة هو طالب (كائن).
- الفئة يمكن أن تكون العناوين عندها كل كائن تابع لهذه الفئة هو عنوان (كائن).
- الفئة يمكن أن تكون النقاط على محاور عندها كل كائن تابع لهذه الفئة هو نُقطة (كائن).
لكل فئة يجب أن يكون ما يلي:
- الصفات والثوابت (תכונות וקבועים): وهي عبارة عن المُعطيات والمُتغيرات التي ستكون للكائنات من هذه الفئة.
- العمليات (פעולות): وهي عمليات تابعة للكائنات التي ستكون من هذه الفئة.
- اسم الطالب (مُتغير يحتوي على بيانات من نوع string)
- رقم هوية الطالب (مُتغير يحتوي على بيانات من نوع int)
- مُعدل الطالب (مُتغير يحتوي على بيانات من نوع double)
- علامات الطالب (مُتغير يحتوي على بيانات من نوع مصفوفة اعداد صحيحة int[ ]).
- الصف (مُتغير يحتوي على بيانات من نوع int)
- التخصص (مُتغير يحتوي على بيانات من نوع string)
أما العمليات في فئة الطلاب يمكن أن تكون:
- انشاء تقرير علامات.
- تغيير الصف (انتقال من صف لآخر).
- حساب المُعدل.
- تعديل علامة.
- طباعة تقرير تفاصيل الطالب.
- اسم المُنتج (مُتغير يحتوي على بيانات من نوع string)
- رقم كود المُنتج (مُتغير يحتوي على بيانات من نوع int)
- سعر المُنتج (مُتغير يحتوي على بيانات من نوع double)
- كمية المُنتج الموجودة بالدكان. (مُتغير يحتوي على بيانات من نوع int)
- اسم الشركة المُصنِّعة لهذا المُنتج. (مُتغير يحتوي على بيانات من نوع string)
- تصنيف المُنتج (مُتغير يحتوي على بيانات من نوع string).
أما العمليات في فئة المُنتجات في دُكان يمكن أن تكون:
- انشاء تقرير عن المُنتج.
- عملية تنبيه عندما يشارف هذا المُنتج على الانتهاء.
- حساب السعر.
- تخفيض أو زيادة.
- عمليات عروض.
- عميات بيع وشراء للمُنتج.
12.2.2 الكائن (עצם/object) – העצם
هو عبارة عن مقطع برمجي تابع لفئة مُعينة يضم بيانات ومُعطيات خاصة بهذا الكائن ويشمل عمليات يمكن أن يجريها هذا الكائن أو تجري على بياناته ومُعطياته. يتم انشاء كائن من نوع فئة مُعينة بواسطة عملية إنشاء كائن (إيجاد ظهور – יצירת מופע) لهذه الفئة.
عند انشاء كائن جديد من نوع فئة معينة يكون لهذا الكائن صفات ومُعطيات هذه الفئة كذلك يكون له كل عمليات هذه الفئة.
12.2.3 كتابة الفئة وكتابة الكائن بلغة السي شارب – כתיבת המחלקה והעצם ב-C#
نبدأ بتعريف الفئة بلغة السي شارب:
نُعرف الفئة بواسطة استعمال الكلمة class وهي كلمة محفوظة باللغة.
class اسم الفئة
{
جسم الفئة ويحتوي على
تعريف الصفات والعمليات
}- نضع تعريف الفئات قبل العملية الرئيسية Main كما أنه يمكننا بعدها.
- داخل الفئات يتم تعريف الصفات والثوابت أولا ثم يتم كتابة عمليات لهذه الفئة.
12.2.4 محددات الوصول (הרשאות גישה) – הרשאות גישה
- يجب كتابة مُحدد الوصول (הרשאות גישה) للصفات والعمليات داخل الفئة.
محدد الوصول يمكن أن يكون:
- public وهو يعني أننا يمكننا الوصول لهذه الصفة او العملية من أي مكان بشكل عام حتى خارج المقطع البرمجي بلوك { } التابع لهذه الفئة، وذلك يُمَكِّن الكائنات المُنشأه من هذه الفئة من الوصول لهذه الصفات والعمليات واستخدامها والتأثير عليها.
- private وهو يعني أننا يمكننا الوصول لهذه الصفة أو العملية فقط من داخل المقطع البرمجي بلوك { } ولا نستطيع التوجه للصفات من خارج هذا البلوك. وذلك لا يُمَكِّن الكائنات المُنشأه من هذه الفئة من الوصول لهذه الصفات والعمليات واستخدامها والتأثير عليها مباشرةً.
12.2.5 العملية البناءة (בנאי) – הבנאי
- مُفضل كتابة عملية بناءة لإنشاء كائنات من هذه الفئة مع صفات بدائية:
العمليات التي سنبنيها مُفضل أن تحتوي على عملية بناء (العملية البناءة) والتي تُمكننا من انشاء كائنات من هذه الفئة.
العملية البناءة يكون اسمها مماثلا لإسم الفئة بالضبط (نفس اسم الفئة) وهي العملية التي نستطيع بواسطتها انشاء كائنات جديدة كل مرة من هذه الفئة. يمكن للعملية البناءة أن تأخذ بين أقواسها قيماً للصفات (كلها أو بعضها) لتُخزنها في الصفات لهذا الكائن كصفات بدائية أو أولية لهذا الكائن عند إنشاؤه ويمكن أن لا تأخذ قيما بالمرة وذلك حسبما نقرر نحن بعملية البرمجة.
عند تعريف عملية البناء يجب أن تكون العملية public أي يمكن للجميع استعمالها وكذلك لا يجدر أن تظهر كلمة void قبل تعريف اسمها وهو اسم الفئة.
إذا لم توجد هذه العملية البناءة فانه يوجد عملية بناء ضمنية من اللغة والتي فيها تكون فيها قيم الصفات للكائن هي قيم صفرية.
12.2.6 عمليات الوصول Set و Get (גישה) – פעולות גישה
- مُفضل كتابة عمليات وصول (גישה) لصفات الفئة.
عمليات الوصول هي عمليات تمنح الكائن الوصول الى صفاته التي عرفناها سابقا على انها private لذلك لا يمكن الوصول لهذه الصفات مباشرة عن طريق الكائن الا بواسطة استخدام عمليات وصول ويجب أن يكون مُحدد الوصول لعمليات الوصول هو public
عمليات الوصول تؤثر على صفات الكائن وتمكننا من قراءتها. يوجد نوعين من عمليات الوصول:
- عمليات وصول تقرأ قيم (تُعطينا قيم) وتسمى Geters وهي عمليات تقرأ وتُعطينا قيم الصفات للكائن.
- عمليات وصول تضع قيم (تُغير قيم) وتسمى Seters وهي عمليات تؤثر على قيم الصفات للكائن ويمكن أن تُغيرها.
- يمكننا كتابة عمليات أخرى داخل الفئة أيضا مثل عمليات طباعة التقارير ....
سنفهم كل ذلك من المثال التالي:
سنبني فئة Car سيارة والتي تمكننا من انشاء كائنات من نوع سيارة أي انشاء سيارة. صفات الفئة هي: نوع السيارة، سعر السيارة ولون السيارة. سنبني عملية بناء للحصول على ظهور كائن جديد (ظهور سيارة) ونبني عمليات وصول Geters و Seters ، كما سنبني عملية تطبع تقريرا عن السيارة.
class Car
{
// الصفات والثوابت
private string kind; // صفة نوع السيارة
private int price; // صفة سعر السيارة
private string color; // صفة لون السيارة
// عملية بناءة
public Car(string kind, int price,string color)
{
this.kind = kind;
this.price = price;
this.color = color;
}
// ****************** عمليات وصول
// ------------- Seters ----------------------------
// Set to kind
public void SetKind(string kind)
{
this.kind = kind;
}
// Set to price
public void SetPrice(int price)
{
this.price = price;
}
// Set to color
public void SetColor(string color)
{
this.color = color;
}
// ------------- Geters ---------------------------
// Get to kind
public string GetKind( )
{
return this.kind;
}
// Get to price
public int GetPrice( )
{
return this.price;
}
// Get to color
public string GetColor( )
{
return this.color;
}
// عمليات أخرى
public void details()
{
Console.WriteLine("The Car is {0} {1}. it worth {2} NIS",kind,color,price);
}
}كما نرى تم تحديد كل صفات الفئة (النوع، السعر، اللون) مع محدد وصول (הרשאת גישה) private وذلك كي لا يكون الوصول لهذه الصفات الا عن طريق المقطع البرمجي (بلوك) ذاته لكل كائن ولا يمكن الوصول لهذه الصفات مباشرة من الكائن الذي ننشئه.
لذلك لتغيير هذه الصفات او قراءتها كتبنا عمليات Set و Get التي تم تعريفها على انها public وهي التي تسمح لنا وللكائن بالوصول لهذه الصفات (النوع، السعر، اللون) من كل مكان بالبرنامج.
استعملنا الكلمة المحفوظة this مع النقطة وهي لكي تعود الى صفة الفئة (أي صفة الكائن).
العملية البناءة بدأت بالكلمة public ثم مباشرة اسم الفئة وهي Car وكتبانها لكي تعطي لكل الصفات قيم بدائية.
عمليات Set تضع البارامترات التي تحصل عليها العملية الى صفات الفئة (صفات الكائن الناتج من هذه الفئة) وذلك باستعمال this التي تعود على صفة الفئة. حيث أننا نستعمل this وذلك للتفرقة بين صفة الفئة وبين البارامتر الذي نحصل عليه لانهما يملكان (قد يملكان) نفس الاسم.
كلمة this تعود على الكائن، وهي مرجع للكائن الذي سوف ننشئه أو للكائن الذي يُشغل العملية. بدون استعمال this سيكون لدينا بلبلة بين صفة الكائن والبارامتر واذا كتبنا مثلا kind=kind; فإن العملية التي ستتم هي تعويض قيمة البارامتر kind في نفسه وليس في صفة الكائن وهذا ليس الذي نرغب به في برمجة الفئة، لذلك تُستعمل كلمة this للإشارة بأن المقصود هو صفة الكائن (الصفة الموجودة بالفئة) وليس البارامتر.
عمليات Get يجب أن نضع فيها الأمر return وذلك لكي تعيد لنا الصفة لكي نقرأها وتعطينا إياها. كما أن عمليات Get لا تأخذ بارامترات.
هكذا بواسطة عمليات الوصول (Set و Get) يمكننا الوصول لصفات الفئة (بالتالي صفات الكائن).
العملية الأخيرة التي كتبناها هي عملية تطبع تفاصيل الكائن. (تفاصيل السيارة).
إذا حذفنا أحد عملية ال Set لصفة معينة فإننا نمنع تغييرها والكتابة لهذه الصفة لأنها private وكذلك إذا حذفنا عملية ال Get لصفة معينة فإننا نمنع قراءتها والوصول اليها.
الان سنتعلم كيف نعرف كائن ونطبق ذلك على المثال السابق:
تعريف كائن يتم بواسطة الإعلان عنه من نوع الفئة المطلوبة. لدينا الفئة هي Car أي علينا كتابة اسم الكائن ونوعه (فئته) على النحو التالي:
اسم الفئة اسم الكائن ;
لبناء هذا الكائن (يجب بناؤه أي انشاء ظهور له مع صفات بدائية حسب الفئة وليس فقط تعريفه) نستعمل اسم الكائن مع العملية new التي رأينا مثلها سابقا في الفئة Random وذلك على النحو التالي:
اسم الكائن = new اسم الفئة( بارامترات حسب الصفات بعملية البناء التي بنيناها);
نستطيع فعل التعريف والبناء بسطر واحد على النحو التالي:
اسم الفئة اسم الكائن = new اسم الفئة(بارامترات حسب الصفات بعملية البناء التي بنيناها);
هكذا يتم انشاء كائن جديد (ظهور كائن جديد) وهنا نكون قد استعملنا عملية البناء التي كتبناها في الفئة وذلك بإرسالنا قيم للصفات (بارامترات) عند استعمال new
سنرى الان كيف سنكتب في برنامجنا الرئيسي تعريف وبناء لسيارتين وسنطبع تفاصيل السيارتين التي انشأناهما ثم سنغير سعر السيارة الأقل ليكون سعرها مثل السيارة الأغلى (سيكون سعر السيارتين نفس المقدار مساوٍ للسعر الأعلى) وسنغير لون المازدا الى أزرق ونطبع تفاصيل السيارات مرة أخرى بعد التغيير.
القيم البدائية للسيارات هي:
السيارة الأولى ستكون سكودا وسعرها 100000 ولونها اسود أما الثانية فستكون مازدا لونها احمر وسعرها 30000
static void Main(string[] args)
{
Car c1, c2; // كائن اسمه c1 وكائن اخر اسمه c2
c1 = new Car("Skoda",100000,"Black");
c2 = new Car("Mazda",30000,"Red");
Console.WriteLine("*****");
c1.details();
c2.details();
Console.WriteLine("** After updating the price to the cars**");
if (c1.GetPrice() > c2.GetPrice())
c2.SetPrice(c1.GetPrice());
else
c1.SetPrice(c2.GetPrice());
c2.SetColor("Blue");
Console.WriteLine("#####");
c1.details();
c2.details();
Console.WriteLine("#####");
}عرفنا السيارة الأولى c1 والسيارة الثانية c2 وبنيناهما بواسطة العملية البناءة التي كتبناها سابقا التي هي:
// عملية بناءة
public Car(string kind, int price,string color)
{
this.kind = kind;
this.price = price;
this.color = color;
}هذه العملية البناءة تتطلب منا ارسال 3 بارامترات عند بناء كائن (سيارة) من فئة Car وهي من نوع جملة نصية string ونوع عدد صحيح int وجملة نصية string وبالترتيب المعروض بتعريفها (الذي نحدده نحن كمبرمجين).
لذلك عرفنا السيارتين وبنيناهما مع المواصفات البدائية المُعطاه على النحو التالي:
Car c1, c2; // كائن اسمه c1 وكائن اخر اسمه c2
c1 = new Car("Skoda",100000,"Black");
c2 = new Car("Mazda",30000,"Red");ننتبه انه لا يمكننا بالوضع الحالي بناء كائنات مع ارسال بارامترات أكثر من ثلاثة أو أقل من ثلاثة أو بترتيب يختلف عن الترتيب أعلاه، وذلك لأنه يوجد تعريف لعملية بَنّاءة واحدة، ولكن يمكننا لاحقا ان نفعل ذلك بعد أن نتعلم مبدأ التحميل (העמסה) وهو يعني كتابة أكثر من عملية بَنّاءة.
المُشغل نقطة هنا يُمكننا من التوجه لصفات كل كائن أو لعمليات الكائن التي تم تعريفها بالفئة وذلك بعد أن نضع النقطة بعد اسم الكائن.
اسم الكائن.صفة
اسم الكائن.العملية( ) ;
لكي نُشغل العملية details التي تتبع للفئة يجب على الكائن الذي أنشأناه أن يُشغلها وهي عملية لا تحصل على بارامترات لذلك شغلناها بواسطة المُشغل نقطة . dot لكل كائن ونفذت المطلوب لصفات كل كائن على حدة.
c1.details(); c2.details();
هذا سيؤدي لطباعة تفاصيل كل سيارة حسب ما عرفنا العملية details والتي كانت:
// عمليات أخرى
public void details()
{
Console.WriteLine("The Car is {0} {1}. it worth {2} NIS",kind,color,price);
}ثم لدينا استعمال لعمليات الوصول Get: و Set حيث أن c1.GetPrice() تُعيد لنا صفة Price للكائن c1 أي سعر السيارة c1 وذلك لان العملية GetPrice( ) كانت كما يلي:
public int GetPrice( )
{
return this.price;
}وهنا this ستعود على صفة الكائن الذي شغل هذه العملية وهو c1
وحيث أن c2.GetPrice() تُعيد لنا صفة Price للكائن c2 أي سعر السيارة c2 .
اما تغيير سعر السيارة فيتم بواسطة العملية SetPrice(int price) والتي يجب أن نرسل لها قيمة صحيحة كبارامتر لكي تعوضها (تضعها) في الصفة الملائمة.
if (c1.GetPrice() > c2.GetPrice())
c2.SetPrice(c1.GetPrice());
else
c1.SetPrice(c2.GetPrice());اذا كان سعر السيارة الأولى أكبر فاننا نشغل عملية SetPrice للسيارة الثانية ونضع فيها السعر الأعلى وهو سعر السيارة الأولى الذي نحصل عليه بواسطة c1.GetPrice() وهو رقم صحيح لانها تعيد الصفة Price للكائن وهي من نوع int أي: c2.SetPrice(c1.GetPrice());
أما إذا لم يكن سعر السيارة الأولى أكبر (أي سعر السيارة الثانية هو الأكبر) سنغير سعر السيارة الأولى لسعر مساوٍ للسيارة الثانية أي: c1.SetPrice(c2.GetPrice());
لتغيير لون السيارة الثانية علينا أن نشغل عملية الوصول للون للكائن c2 وهي العملية SetColor والتي نرسل لها بارامتر من نوع جملة نصية string الذي هو لون السيارة.
c2.SetColor("Blue");ثم بعد ذلك نطبع تفاصيل هذه السيارات من جديد لنرى أن صفاتها تغيرت وذلك بمساعدة عملية البناء وعمليات الوصول Set و Get
وسيظهر لنا على الشاشة ما يلي:
***** The Car is Skoda Black. it worth 100000 NIS The Car is Mazda Red. it worth 30000 NIS ** After updating the price to the cars** ##### The Car is Skoda Black. it worth 100000 NIS The Car is Mazda Blue. it worth 100000 NIS ##### Press any key to continue . . .
ملاحظات:
- لا يمكننا أن نتوجه مباشرة بواسطة الكائن لصفاته بالطريقة التالية:
c2.color = "Blue";وذلك لان الصفة color تم تعريفها على أنها private ولكن لو عرفناها public لكان بالإمكان فعل ذلك. وهنا تكمن أهمية مُحددات التوجه private حيث يتم عدم اتاحة تغيير هذه الصفات من الكائن نفسه دون رقابة على ذلك، وتتم الرقابة على ذلك بواسطة استعمال عمليات Get و Set . - العمليات جعلناها public لتكون متاحة للكائن من كل مكان.
- اذا اردنا ان نحافظ على صفة دون إمكانية تغييرها نستطيع أن نحذف عملية Set لهذه الصفة والتي نعرفها private عندها لن تكون هناك إمكانية لوضع قيم لهذه الصفة، الا بواسطة عمليات البناء اذا تم تعريف وضع قيم لهذه الصف في عملية البناء.
- بناء الكائن تتم فقط مرة واحدة لكل كائن ولا يمكن استعمال عملية البناء مرة أخرى لكائن تم إنشاؤه لتعديل قيم الصفات.
12.3 العمليات البناءة – הבנאים
اذا لم نكتب عملية بناءة فانه يوجد عملية بناءة ضمنية باللغة. وهي عملية بناءة تبني الكائن مع كل صفات الفئة دون إعطاء الصفات قيمة بدائية مُعينة، انما يتم إعطاء قيمة بدائية صفرية لكل الصفات.
عندها لبناء كائن وتعريفه نستطيع كتابة ما يلي:
اسم الفئة اسم الكائن = new اسم الفئة( ) ;
ننتبه أن الأقواس في عملية البناء فارغة ولا نرسل لها قيماً.
اذا كتبنا عملية بناءة مُعينة فان الحاسوب يُلغي العملية البناءة الضمنية ولا يمكننا عندها انشاء كائنات بدون ارسال قيم حسب العملية البناءة التي بنيناها.
من هنا يمكن القول أننا نستطيع عدم كتابة عملية بناءة ولكن عند انشاء كائن جديد (ظهور كائن) نستعمل العملية new واسم الفئة دون ارسال أي قيم في الأقواس ويُمنع ارسال قيم كبارامترات للكائن عن طريق العملية البناءة الضمنية من اللغة. ولكن اذا بنينا عملية بناءة كتبنا فيها أن الكائن الجديد يأخذ الصفات كبارامترات فانه علينا ارسالها ولا يجوز عندها كتابة الأقواس فارغة وعدم ارسال قيم.
بالإمكان إعادة كتابة عملية البناء الضمنية من قبلنا اذا اردناها بالإضافة لعملية البناء التي كتبناها ونستطيع كتابة عمليات بناء أخرى (أكثر من طريقة لبناء كائن وانشاؤه) وذلك بان كل عملية تختلف عن الأخرى بالبارامترات التي ستأخذها هذه العملية لإنشاء كائن جديد.
كتابة العديد من عمليات البناء يسمى التحميل الزائد (העמסה)، وهو مبدأ يعمل أيضا للعمليات.
كمثال على ذلك سنعرف فئة طالب ثانوية بواسطة صفتين: الاسم والصف كما يلي:
class Student
{
private string name;
private int the_class;
//Seters
public void SetName(string name)
{
this.name = name;
}
public void SetClass(int the_class)
{
this.the_class = the_class;
}
//Geters
public string GetName()
{
return this.name;
}
public int GetClass()
{
return this.the_class;
}
}
static void Main(string[] args)
{
Student s1 = new Student();
Student s2;
s2 = new Student();
Console.WriteLine("{0} from class: {1}", s1.GetName(), s1.GetClass());
Console.WriteLine("{0} from class: {1}", s2.GetName(), s2.GetClass());
s1.SetName("Hammam");
s2.SetName("Abd");
s1.SetClass(11);
s2.SetClass(10);
Console.WriteLine("{0} from class: {1}",s1.GetName(),s1.GetClass());
Console.WriteLine("{0} from class: {1}",s2.GetName(),s2.GetClass());
}كما يظهر لدينا فإننا لم نكتب أي عمليات بناء ولم نكتب أي عمليات الا عمليات الوصول (Set و Get) لذلك في البرنامج عندما أردنا انشاء كائن عرفناه بواسطة الأمر new وكتبنا اسم الفئة ولم نرسل أي بارامترات للكائن الجديد أي تركنا الأقواس فارغة.
يظهر لدينا بالبرنامج أننا أنشأنا كائنين جدد هما s1 و s2 وصفاتهما هي صفات صفرية نابعة من عملية البناء الضمنية باللغة. لذلك عندما نطبع تفاصيل هذين الكائنين تكون قيم صفاتهما صفرية.
ثم بواسطة عمليات الوصول نضع قيم للطالب الأول (اسمه همام وهو بالصف 11) والطالب الثاني (اسمه عبد وهو بالصف 10) ونطبع الصفات ويظهر لدينا ان الكائنين اكتسبا هذه الصفات.
كما يظهر بالمُخرج التالي:
from class: 0 from class: 0 Hammam from class: 11 Abd from class: 10
الان سنضيف عملية بناءة تأخذ كل الصفات وسنرى أن عملية البناء الضمنية ستُلغى ولن نتمكن من استعمالها.
عملية البناء هي:
// عملية بناء
public Student(string name, int the_class)
{
this.name = name;
this.the_class = the_class;
}حيث سيصبح لدينا:
class Student
{
private string name;
private int the_class;
// عملية بناء
public Student(string name, int the_class)
{
this.name = name;
this.the_class = the_class;
}
//Seters
public void SetName(string name)
{
this.name = name;
}
public void SetClass(int the_class)
{
this.the_class = the_class;
}
//Geters
public string GetName()
{
return this.name;
}
public int GetClass()
{
return this.the_class;
}
}
static void Main(string[] args)
{
Student s1 = new Student(); // ERROR : We can`t
Student s2;
s2 = new Student(); // Error: We can`tهنا سيكون لدينا خطأ برمجي وذلك بالبرنامج عند انشاء كائن جديد بواسطة new لأننا يجب أن نرسل بارامترات كما عرفنا عملية البناء، لذلك يمكننا ان نكتب ونصحح البرنامج على ما يلي:
static void Main(string[] args)
{
Student s1 = new Student("",0);
Student s2;
s2 = new Student("Abd",10);
Console.WriteLine("{0} from class: {1}", s1.GetName(), s1.GetClass());
Console.WriteLine("{0} from class: {1}", s2.GetName(), s2.GetClass());
s1.SetName("Hammam");
s1.SetClass(11);
Console.WriteLine("{0} from class: {1}",s1.GetName(),s1.GetClass());
Console.WriteLine("{0} from class: {1}",s2.GetName(),s2.GetClass());
}للطالب الأول ارسلنا قيم صفرية وكنا نستطيع ان نرسل قيم بدائية للصفات، لذلك احتجنا استعمال عملية الوصول set لتغيير هذه الصفات. بينما للطالب الثاني عند إنشائه ارسلنا القيم الصحيحة البدائية للصفات وهي اسمه وصفه.
وعند التشغيل سيظهر:
from class: 0 Abd from class: 10 Hammam from class: 11 Abd from class: 10
إذا اردنا إعادة عملية البناء الضمنية يجب أن نكتبها بالإضافة لعملية البناء التي كتبناها حيث سيكون لدينا عمليتا بناء وليس واحدة فقط حيث ستكون عمليتا البناء كما يلي:
// عملية بناء
public Student(string name, int the_class)
{
this.name = name;
this.the_class = the_class;
}
// اعادة كتابة عملية البناء الضمنية لاننا اضفنا عملية بناء
public Student()
{
}البرنامج سيكون على النحو التالي:
class Student
{
private string name;
private int the_class;
// عملية بناء
public Student(string name, int the_class)
{
this.name = name;
this.the_class = the_class;
}
// اعادة كتابة عملية البناء الضمنية لاننا اضفنا عملية بناء
public Student()
{
}
//Seters
public void SetName(string name)
{
this.name = name;
}
public void SetClass(int the_class)
{
this.the_class = the_class;
}
//Geters
public string GetName()
{
return this.name;
}
public int GetClass()
{
return this.the_class;
}
}
static void Main(string[] args)
{
Student s1 = new Student();
Student s2;
s2 = new Student("Abd",10);
Console.WriteLine("{0} from class: {1}", s1.GetName(), s1.GetClass());
Console.WriteLine("{0} from class: {1}", s2.GetName(), s2.GetClass());
s1.SetName("Hammam");
s1.SetClass(11);
Console.WriteLine("{0} from class: {1}",s1.GetName(),s1.GetClass());
Console.WriteLine("{0} from class: {1}",s2.GetName(),s2.GetClass());
}نرى أن انشاء الكائن الأول s1 تم بواسطة عملية البناء الضمنية التي أعدنا كتابتها بينما الكائن الثاني تم بواسطة عملية البناء التي تأخذ كل الصفات كبارامترات
الان يُمكننا أيضا كتابة عملية بناء إضافية تأخذ بارامتراً واحدا فقط (مثلا الاسم) عندها يمكن استعمالها لبناء كائن مع ارسال الاسم فقط. ويمكننا إضافة عملية بناء أخرى تأخذ بارامتراً واحدا فقط (مثلا الصف) عندها يمكن استعمالها لبناء كائن مع ارسال الصف فقط.
يمكن أيضا كتابة عمليات بناء تعطي قيم الصفات البدائية (كلها أو بعضها) من داخل تعريف عملية البناء (من المُبرمج) ولا تأخذها من المُستخدم لا كبارامترات ولا كصفات بدائية ويمكن تغيرها بعد انشاء الكائن بواسطة عمليات الوصول.
سنكتب عملية بناء إضافية (مثلا تأخذ الاسم فقط كبارامتر، لذلك الصفة الأخرى وهي الصف ستكون صفرية لأن أي صفة لا تدرج في عملية البناء التي نكتبها ستحصل على قيم صفرية، وهذا يفسر عملية البناء الضمنية باللغة التي لا تحصل على أي بارامتر ولديها اقواس فارغة) كما يلي:
// عملية بناء تستقبل بارامتر واحد فقط وهو الاسم
public Student(string name)
{
this.name = name;
}لذلك تصبح الفئة على ما يلي:
class Student
{
private string name;
private int the_class;
// عملية بناء
public Student(string name, int the_class)
{
this.name = name;
this.the_class = the_class;
}
// اعادة كتابة عملية البناء الضمنية لاننا اضفنا عملية بناء
public Student()
{
}
// عملية بناء تستقبل بارامتر واحد فقط وهو الاسم
public Student(string name)
{
this.name = name;
}
//Seters
public void SetName(string name)
{
this.name = name;
}
public void SetClass(int the_class)
{
this.the_class = the_class;
}
//Geters
public string GetName()
{
return this.name;
}
public int GetClass()
{
return this.the_class;
}
}أما برنامجنا الأساسي سيكون:
static void Main(string[] args)
{
Student s1 = new Student();
Student s2;
s2 = new Student("Abd",5);
Student s3 = new Student("Mriam");
Console.WriteLine("{0} from class: {1}", s1.GetName(), s1.GetClass());
Console.WriteLine("{0} from class: {1}", s2.GetName(), s2.GetClass());
Console.WriteLine("{0} from class: {1}", s3.GetName(), s3.GetClass());
s1.SetName("Hammam");
s1.SetClass(11);
s3.SetClass(3);
Console.WriteLine("{0} from class: {1}",s1.GetName(),s1.GetClass());
Console.WriteLine("{0} from class: {1}",s2.GetName(),s2.GetClass());
Console.WriteLine("{0} from class: {1}", s3.GetName(), s3.GetClass());
}والمُخرج سيكون:
from class: 0 Abd from class: 5 Mriam from class: 0 Hammam from class: 11 Abd from class: 5 Mriam from class: 3 Press any key to continue . . .
12.4 مثال لمُحددات الوصول وعمليات الوصول (Set و Get) واهمية العملية البناءة – דוגמה להרשאות הגישה ולחשיבות הבנאי
سنبني فئة مستطيلات وستكون صفات الفئة:
- الطول: عدد عشري (double).
- العرض: عدد عشري (double).
سنُعَّرِف الصفات في البداية على أنها public وسنرى أنه بإمكاننا انشاء مستطيلات والوصول للصفات لوضع قيم فيها أو لقراءتها من البرنامج الرئيسي (خارج الفئة)، يمكننا فعل ذلك لان الصفات ذات محدد وصول public أي نستطيع الوصول للصفات من أي مكان دون تحديد.
في البداية لن نعرف عملية بناءة (سنعتمد على عملية البناء الضمنية) وسنبني العمليات التالية:
- عملية تحسب مساحة المستطيل. (تُعيد المساحة double)
- عملية تحسب محيط المستطيل. (تُعيد المحيط double)
- عملية ترسم المستطيل. (لا تُعيد أي قيم)
- عملية تقرر إذا كان المستطيل هو عبارة عن مربع (حالة خاصة) أم لا. (تُعيد قيمة بوليانية true إذا كان المستطيل مربعا أو تعيد false إذا لم يكن المستطيل مربعاً)
public class Rectangle
{
public double length, width;
public double Area()
{
return this.length*this.width;
}
public double Perimeter()
{
return 2 * (this.length + this.width);
}
public void draw()
{
for(int i=1;i<=this.length;i++)
{
for (int j = 1; j <= this.width; j++)
Console.Write("*");
Console.WriteLine();
}
}
public bool IsSquare()
{
if (this.length == this.width)
return true;
else
return false;
}
}لا نحتاج لكتابة عمليات وصول لأننا نستطيع الوصول للصفات بواسطة المُشغل نقطة (dot) حيث نكتب اسم الكائن ثم نقطة ونكتب الصف ونتعامل معها كمتغير نستطيع قراءته وتغير قيمته من أي مكان كما سنرى بالبرنامج التالي الذي سنبني فيه كائنين (مستطيلين r1 وr2) سنحدد أطوالهما على أن المستطيل الأول طوله 5 وعرضه 5 بينما الثاني طوله 4 وعرضه 2.
static void Main(string[] args)
{
double area, d;
Rectangle r1 = new Rectangle();
//Rectangle r2 = new Rectangle(4,2); خطأ ERORR
Rectangle r2 = new Rectangle();
r1.length = 5;
r1.width = 5;
r2.length = 4;
r2.width = 2;
Console.WriteLine("rectangle 1 is: {0}X{1}", r1.length,r1.width);
Console.WriteLine("rectangle 2 is: {0}X{1}", r2.length, r2.width);
area = r1.Area();
d = r1.Perimeter();
Console.WriteLine("The area of first rectangle is: {0}",area,d);
Console.WriteLine("The perimeter of second rectangle is:{0}",r2.Perimeter());
Console.WriteLine("-------- NOW WE WILL DRAW THE RECTANGLES --------");
Console.WriteLine("Rectangle 1: ");
r1.draw();
Console.WriteLine("Rectangle 2: ");
r2.draw();
if(r1.IsSquare())
Console.WriteLine("Rectangle 1 is also SQUARE");
if(r2.IsSquare())
Console.WriteLine("Rectangle 2 is also SQUARE");
}ملاحظات مهمة:
- لا يجد بنا أن نحدد الصفات على أنها public كي تكون محمية من الوصول اليها.
- بما أننا لم نكتب العملية البناءة التي تأخذ صفات الكائن كبارامترات استعملنا عملية البناء الضمنية (ברירת המחדל – default constructor) ولذلك احتجنا أن نُدخل قيم الصفات لكل كائن، صفة تلو صفة وإذا كانت الصفات private سنحتاج أن نستعمل عمليات Set لكل صفة على حدة مما قد يسبب لطول البرنامج ويأخذ الكثير من الوقت، لذلك من المُفضل كتابة عملية بناء (حتى لو حُذفت الضمنية – فنحن نستطيع كتابتها أو نتخلى عنها)
النتيجة بعد التشغيل:
rectangle 1 is: 5X5 rectangle 2 is: 4X2 The area of first rectangle is: 25 The perimeter of second rectangle is:12 -------- NOW WE WILL DRAW THE RECTANGLES -------- Rectangle 1: ***** ***** ***** ***** ***** Rectangle 2: ** ** ** ** Rectangle 1 is also SQUARE Press any key to continue . . .
سنغير الصفات من public الى private لذلك يجب أن نكتب عمليات وصول لأنه بلا عمليات الوصول سيكون لدينا Error في الأوامر التي نصل فيها للصفات مباشرة. سنكتب أيضا عملية بناء تأخذ الصفات.
سنحصل على الفئة بهذا الشكل:
public class Rectangle
{
private double length, width;
public Rectangle(double length,double width) // عملية بناءة
{
this.length = length;
this.width = width;
}
//Seters
public void SetLength(double length)
{
this.length = length;
}
public void SetWidth(double w) // we can call the parameter as we want
{
this.width = w;
}
//Geters
public double GetLength()
{
return this.length;
}
public double GetWidth()
{
return this.width;
}
//Methods
public double Area()
{
return this.length*this.width;
}
public double Perimeter()
{
return 2 * (this.length + this.width);
}
public void draw()
{
for(int i=1;i<=this.length;i++)
{
for (int j = 1; j <= this.width; j++)
Console.Write("*");
Console.WriteLine();
}
}
public bool IsSquare()
{
if (this.length == this.width)
return true;
else
return false;
}
}سنكتب البرنامج السابق مع بعض التعديلات لكي يتلائم مع محددات الوصول وعملية البناء. وذلك لانه يجب أن ننتبه لتغيير بعض الأوامر السابقة والتي ستصبح الان خطأ برمجي بسبب تغيراتنا على محددات الوصول وهي ما يلي:
//Rectangle r1 = new Rectangle(); Error //Rectangle r2 = new Rectangle(); Error //r1.length = 5; Error //r1.width = 5; Error //r2.length = 4; Error //r2.width = 2; Error
أما البرنامج الرئيسي بعد التعديلات فسيكون:
static void Main(string[] args)
{
double area, d;
Rectangle r1 = new Rectangle(5,5);
Rectangle r2 = new Rectangle(4,2); // it is Correct now
Console.WriteLine("rectangle 1 is: {0}X{1}", r1.GetLength(),r1.GetWidth());
Console.WriteLine("rectangle 2 is: {0}X{1}", r2.GetLength(), r2.GetWidth());
area = r1.Area();
d = r1.Perimeter();
Console.WriteLine("The area of first rectangle is: {0}",area,d);
Console.WriteLine("The perimeter of second rectangle is: {0}", r2.Perimeter());
Console.WriteLine("-------- NOW WE WILL DRAW THE RECTANGLES --------");
Console.WriteLine("Rectangle 1: ");
r1.draw();
Console.WriteLine("Rectangle 2: ");
r2.draw();
if(r1.IsSquare())
Console.WriteLine("Rectangle 1 is also SQUARE");
if(r2.IsSquare())
Console.WriteLine("Rectangle 2 is also SQUARE");
}نتيجة التشغيل ستبقى كما كانت سابقاً.
rectangle 1 is: 5X5 rectangle 2 is: 4X2 The area of first rectangle is: 25 The perimeter of second rectangle is: 12 -------- NOW WE WILL DRAW THE RECTANGLES -------- Rectangle 1: ***** ***** ***** ***** ***** Rectangle 2: ** ** ** ** Rectangle 1 is also SQUARE Press any key to continue . . .
12.5 مواضيع مُتقدمة: (مواضيع اختيارية – جزء منها مطلوب في منهاج الوزارة) – נושאים מתקדמים
12.5.1 المصفوفة الأحادية كصفة من صفات الفئة – מערך חד-ממדי כתכונה של המחלקה
سنشرح الموضوع بواسطة تطبيق الاستخدام في مثال.
مثال:
طلاب (لكل طالب اسم شخصي ورقم هوية) يدرسون في كلية للعلوم، في كل سنة يتعلم الطالب 6 مواضيع مُختلفة.
يُعتبر الطالب ناجحا في السنة الدراسية إذا كان مُعدله في السنة (مُعدل الستة مواضيع) أعلى من 65 وأيضا لم يفشل أكثر من موضوعين من المواضيع الستة للسنة الدراسية (الفشل في الموضوع الدراسي هو علامة أقل من 55).
سنحل المسألة على مراحل كما يلي:
(أ) اكتب فئة طالب Student فيها الصفات التالية:
name - الاسم من نوع جملة نصية
id - الهوية من نوع عدد صحيح
marks – العلامات من نوع مصفوفة اعداد عشرية طولها 6.
average - المُعدل من نوع عدد عشري.
الحل:
class Student
{
private string name;
private int id;
private double[] marks = new double[6];
private double avarage;
}(ب) أضف عملية بناء تأخذ فقط الصفتين: الاسم والهوية.
الحل:
public Student(string name, int id)
{
this.name = name;
this.id = id;
}(ج) أضف العلميات التالية:
- عمليات وصول:
seters فقط للصفة العلامات (استقبال 6 علامات للمصفوفة).
لن نستقبل صفة المُعدل لأنه يتم حسابها بالاعتماد على صفة المصفوفة (علامات المصفوفة).
لن نستقبل الاسم أو الهوية في عملية وصول set لأننا نرسلها عند انشاء طالب (كائن) جديد.
geters لكل الصفات. (انتبه أن أحد هذه الصفات هو مصفوفة).
الحل:
عملية استقبال مصفوفة كاملة وهي صفة من صفات الكائن.
public void setmarks(double[] marks)
{
this.marks = marks;
}أما عمليات get فهي كالتالي:
public string getname()
{
return this.name;
}
public int getid()
{
return this.id;
}
public double[] getmarks()
{
return this.marks;
}
public double getavarage()
{
return this.avarage;
}(د) أضف العمليات التالية:
- عملية حساب المُعدل بالاعتماد على علامات الطالب من صفة مصفوفة العلامات.
- عملية تفحص إذا كان مُعدل الطالب هو مُعدل النجاح أم لا (اذا كان المُعدل هو مُعدل النجاح فان العملية تُعيد true ، اذا لم يكن مُعدل الطالب مُعدل نجاح فان العملية تُعيد false) ومعلوم أن مُعدل النجاح هو اذا كان مُعدله أعلى من 65 .
- عملية تفحص إذا كان الطالب قد نجح أم فشل في مواضيع التعليم حيث أنه اذا فشل الطالب بأكثر من موضوعين يتم اعتباره قد فشل في المواضيع التعليمية (الفشل في موضوع مُعين هو اذا حصل على علامة أقل من 55 في هذا الموضوع) اذا كان قد فشل الطالب في المواضيع التعليمية على العملية أن تُعيد true اما اذا لم يفشل فإنها تُعيد false .
الحل:
public double Average()
{
int i;
double sum = 0;
for (i = 0; i < 6; i++)
{
sum += this.marks[i];
}
this.avarage = sum / 6;
return sum / 6;
}
public bool checkavarage()
{
if (this.avarage <= 65)
return false;
else
return true;
}
public bool checkfails()
{
int cntr=0,i;
for(i=0;i<6;i++)
{
if (this.marks[i] < 55)
cntr++;
}
if (cntr > 2)
return true;
else
return false;
}12.5.2 مصفوفة كائنات – מערך עצמים
سنكمل المثال لكي نشرح هذا الموضوع أيضا.
(هـ) اكتب برنامج يُعرف:
- مصفوفة كائنات من نوع طالب طولها N حيث أن N يكون ثابت (ممكن اعطاؤه أي قيمة عددية تريد).
- مصفوفة علامات طولها 6 خانات ستحتوي علامات الطالب التي سنستقبلها بالبرنامج كي نرسلها كمصفوفة الى عملية الوصول set صفة مصفوفة العلامات للكائن.
- على البرنامج أن يملأ مصفوفة الكائنات (الطلاب) باستقبال اسم الطالب وهويته كصفات للكائن حيث يكون كل كائن في خانة من خانات المصفوفة.
- على البرنامج أن يستقبل علامات الطلاب الى داخل المصفوفة (كل طالب على حده) ثم يرسل هذه المصفوفة كصفة الى الكائن الموجود في مصفوفة الطلاب. (انتبه أن مصفوفة الطلاب هي مصفوفة كائنات)
- على البرنامج أن يفحص اذا كان وضح الطالب الاكاديمي هو نجاح (תקין = TKIN) أو وضعه الأكاديمي سقوط (לא תקין = "MSH TKIN"). هذا يتطلب استعمال عمليات حساب المعدل لكل طالب وملأ صفة المعدل لدى كل طالب ويتطلب استعمال العمليات المتاحة لكل طالب بالنسبة لوضعه السنوي من ناحية نجاحه في معدله السنوي ونجاحه في مواضيعه التدريسية.
- في النهاية على البرنامج أن يطبع تقريرا عن كل طالب (اسمه وهويته) ووضعه الاكاديمي (TKIN أو MSH TKIN) ومُعدله.
الحل:
static void Main(string[] args)
{
const int N = 1; // number of students
string name,AS;
int id,i;
double avg;
Student[] students = new Student[N];
for (i = 0; i < N; i++)
{
Console.Write("Give me the name of the {0} student: ",i+1);
name = Console.ReadLine();
Console.Write("Give me the id of the {0} student:",i+1);
id = int.Parse(Console.ReadLine());
students[i] = new Student(name, id);
}
for (int j = 0; j < N; j++)
{
double[] Marks = new double[6];
for (i = 0; i < 6; i++)
{
Console.Write("give me the mark number {0} of the {1} student:",i + 1,j+1);
Marks[i] = double.Parse(Console.ReadLine());
}
students[j].setmarks(Marks);
students[j].Average();
}
for (i = 0; i < N; i++)
{
if(students[i].checkavarage() && !students[i].checkfails())
AS = "TKIN";
else
AS = "MSH TKIN";
Console.WriteLine(students[i].getname() + "[id:" + students[i].getid() + "] --> Academic Situation: " + AS + " Average:" + students[i].getavarage());
}
Console.ReadKey();
}سنكتب الان كل الفئة والعمليات والبرنامج معا كقطعة واحدة حيث سنحصل على ما يلي:
using System;
namespace ConsoleApp50
{
class Student
{
private string name;
private int id;
private double[] marks = new double[6];
private double avarage;
public Student(string name, int id)
{
this.name = name;
this.id = id;
}
public void setmarks(double[] marks)
{
this.marks = marks;
}
public double Average()
{
int i;
double sum = 0;
for (i = 0; i < 6; i++)
{
sum += this.marks[i];
}
this.avarage = sum / 6;
return sum / 6;
}
public bool checkavarage()
{
if (this.avarage <= 65)
return false;
else
return true;
}
public bool checkfails()
{
int cntr=0,i;
for(i=0;i<6;i++)
{
if (this.marks[i] < 55)
cntr++;
}
if (cntr > 2)
return true;
else
return false;
}
public string getname()
{
return this.name;
}
public int getid()
{
return this.id;
}
public double[] getmarks()
{
return this.marks;
}
public double getavarage()
{
return this.avarage;
}
}
class Program
{
static void Main(string[] args)
{
const int N = 1; // number of students
string name,AS;
int id,i;
double avg;
Student[] students = new Student[N];
for (i = 0; i < N; i++)
{
Console.Write("Give me the name of the {0} student:",i+1);
name = Console.ReadLine();
Console.Write("Give me the id of the {0} student:",i+1);
id = int.Parse(Console.ReadLine());
students[i] = new Student(name, id);
}
for (int j = 0; j < N; j++)
{
double[] Marks = new double[6];
for (i = 0; i < 6; i++)
{
Console.Write("give me the mark number {0} of the {1} student:", i + 1,j+1);
Marks[i] = double.Parse(Console.ReadLine());
}
students[j].setmarks(Marks);
students[j].Average();
}
for (i = 0; i < N; i++)
{
if(students[i].checkavarage() && !students[i].checkfails())
AS = "TKIN";
else
AS = "MSH TKIN";
Console.WriteLine(students[i].getname() + "[id:" + students[i].getid() + "] --> Academic Situation: " + AS + " Average:" + students[i].getavarage());
}
Console.ReadKey();
}
}
}12.5.3 التعليب أو التغليف (כימוס/Encapsulation) – כימוס
هي تقنية هدفها إخفاء الصفات والعمليات الداخلية حيث يتم تحديد مُحددات التوجه أن تكون private ثم بناء واجهة استخدام (ממשק) تسمح لبعض الصفات المهمة أو العمليات المهمة قابلة للوصول بواسطة تعريف عمليات public أو عمليات وصول Set و Get وبذلك يتم تغليف الفئة وتعليبها وعدم اتاحة كل محتوياتها للمستخدمين الا فقط ما يتم تحديده من البرنامج.
12.5.4 ارسال كائن كبارامتر لعملية خارجية – שליחת עצם כפרמטר לפעולה חיצונית
كما تعلمنا سابقا في فصل العمليات الخارجية أن العملية يمكن أن تحصل على بارامترات من أنواع مختلفة كمُدخلات لها تتلقاها عند استدعاءها من الذي يستدعيها ويشغلها.
بإمكاننا ارسال كائن (قد تم اشاؤه وفيه صفات وعمليات وصول وعمليات تابعه له) للعملية وذلك بارسال اسمه للعملية ضمن قائمة بارامترات العملية ولكن قبل ذلك على العملية أن تكون مُعرفة انها تستقبل كائن في بارامتراتها.
نفرض أننا عرفنا الفئة Book أي كتبنا ما يلي:
class Book
{
.....
}داخل الفئة تم تعريف صفات وعملية بناء وعمليات وصول وعمليات تابعة للكائنات من فئة Book.
إذا اردنا تعريف عملية خارجية اسمها ForChildren تستقبل الكائن كبارامتر وتعيد قيمة بوليانية مثلا نكتب العملية بالشكل التالي:
static bool ForChildren(Book b)
{
}عند استدعاء هذه العملية يمكننا أن نرسل لها كائنا، مثلا اذا كان لدينا كائن (قد تم إنشاؤه ولديه صفات) يمكن ارسال اسمه للعملية عند استدعاءها مثلا:
Book b1 = new Book("The Lion King", 17, true, "L.James");
Book b2 = new Book("C#", 204, true, "Hammam");
if(ForChildren(b1)==true)في المثال التالي سيتم توضيح ذلك:
- سنبني فئة Book التي لها الصفات:
- title – من نوع string يحتوي عنوان الكتاب.
- pages – من نوع int يحتوي عدد صفحات الكتاب.
- color – من نوع bool يحتوي true اذا كان الكتاب ملوناً، خلاف ذلك false.
- author – من نوع string يحتوي اسم مؤلف الكتاب.
- سنبني عملية بناء ونبني كل عمليات الوصول بالإضافة لعملية تابعة للفئة تُعيد جملة نصية فيها معلومات الكتاب التالية: (اسم الكتاب واسم مؤلفه وعدد صفحاته).
- سنبني عملية خارجية اسمها ForChildren تتلقى كائنات من نوع Book كبرامتر وتُعيد قيمة بوليانية true إذا كان الكتاب مُخصصا للأطفال أو تُعيد false إذا كان الكتاب للكبار. معلوم أن كتاب الأطفال يجب أن يكون ملوناً وعدد صفحاته يجب أن تكون أقل من 50.
الحل:
class Book
{
private string title;
private int pages;
private bool color;
private string author;
public Book(string title,int pages,bool color,string author)
{
this.title = title;
this.pages = pages;
this.color = color;
this.author = author;
}
public void SetTitle(string title)
{
this.title = title;
}
public void SetPages(int pages)
{
this.pages = pages;
}
public void SetColor(bool color)
{
this.color = color;
}
public void SetAuthor(string author)
{
this.author = author;
}
public string GetTitle()
{
return this.title;
}
public int GetPages()
{
return this.pages;
}
public bool GetColor()
{
return this.color;
}
public string GetAuthor()
{
return this.author;
}
public string BookInfo()
{
return "Book: " + this.title + "(" + this.author + ") " + this.pages + " Pages.";
}
}
static bool ForChildren(Book b)
{
if (b.GetColor() == true && b.GetPages() < 50)
return true;
else
return false;
}- الان سنبني بالبرنامج الرئيسي كائنين (ننشئ كائنين) نُعطيهما قيما بدائية (كمثال) ونستدعي العملية الخارجية ForChildren ونرسل اليها كل كتاب (كل كائن منهما) وإذا كان الكتاب مُخصصا للأطفال علينا طباعة تفاصيله المُهمة على الشاشة.
الحل:
static void Main(string[] args)
{
Book b1 = new Book("The Lion King", 17, true, "L.James");
Book b2 = new Book("C#", 204, true, "Hammam");
if(ForChildren(b1)==true)
Console.WriteLine(b1.BookInfo());
if (ForChildren(b2) == true)
Console.WriteLine(b2.BookInfo());
}مثال آخر:
مُعطى الفئة Clock التي تُعرف بيانات من نوع ساعة.
public class Clock
{
private int hour; //0-23
private int min; //0-59
public Clock(int hour, int min)
{
this.hour = hour;
this.min = min;
}
public int GetHour()
{
return this.hour;
}
public void SetMin(int min)
{
this.min = min;
}
}(أ) أكمل كتابة عمليات الوصول في هذه الفئة.
(ب) اكتب عملية خارجية (static) اسمها IsEqualClocks. هذه العملية تحصل على ساعتين (كائنين من نوع ساعة) كبارامترات وتفحص اذا كانت الساعتان يشيران لنفس الوقت.
(ج) في المطار يتم اتخاذ إجراءات السلامة بحيث يُمنع ان يكون هناك هبوط لأكثر من طائرة بنفس الوقت. برج المراقبة يفحص أزمنة الهبوط للطائرة القادمة للمطار ويُعطي لهم الاذن بالهبوط في حالة أنه لا يوجد تتطابق أزمنة أو لا يُعطي الاذن اذا كانت ازمنة الهبوط متطابقة.
في البرنامج الرئيسي Main الذي تم فيه تعريف مصفوفة تحتوي أوقات هبوط اسمها times (المصفوفة تحتوي على كائنات t1,t2,t3,... من نوع Clock لساعات هبوط الطائرة القادمة)، اكتب مقطع برمجي (أوامر تُنشئ كائن جديد من نوع ساعة وتستقبل من المُستخدم زمن هبوط طائرته (طائرة أخرى غير التي بالمصفوفة). المقطع يفحص ويطبع على الشاشة اذا كان هناك خطر التصادم أم لا.
(يجب أن تستخدم العملية التي كتبتها بالفرع السابق).
لا حاجة لفحص صحة المُدخلات. ولا حاجة لتعريف أو بناء المصفوفة times ولا ادخال مُعطيات لها.
الحل:
(أ)
public int GetMin()
{
return this.min;
}
public void SetHour(int hour)
{
this.hour = hour;
}
public override string ToString()
{
string h = ""+this.hour;
if (this.hour < 10) h = "0" + h;
string m = "" + this.min;
if (this.min < 10) m = "0" + m;
return h + ":" + m;
}(ب)
public static bool IsEqualClocks(Clock c1, Clock c2)
{
if(c1.GetHour()==c2.GetHour() &&
c1.GetMin()==c2.GetMin())
return true;
return false;
}(ج)
int h = int.Parse(Console.ReadLine());
int m = int.Parse(Console.ReadLine());
Clock c = new Clock(h, m);
bool flag = false;
for(int i=0; i<times.Length; i++)
{
if(IsEqualClocks(times[i],c)) flag=true;
}
if(flag)
Console.WriteLine("Danger of collision");
else
Console.WriteLine("There is no danger of collision");12.6 تمارين محلولة – תרגילים ופתרונות
(أ)
اكتب فئة اسمها Client التي تُعرف زبائن للمتابعة لدى اخصائية تغذية (דיאטנית).
صفات الزبائن هي:
- اسم.
- رقم هوية.
- وزن بدائي (أولي) وهو الوزن عند التسجيل لدى اخصائية التغذية وبداية البرنامج الغذائي.
- الوزن الاسبوعي.
- الطول.
- تكلفة العلاج (الدفع).
اكتب عملية بناء تحصل على الاسم ورقم الهوية والوزن البدائي والطول.
اكتب عمليات وصول للصفات الملائمة. (لا يوجد set لتكلفة العلاج لانها قيمة يتم حسابها، كذلك لا يوجد set للوزن البدائي).
اكتب عملية تحسب تكلفة العلاج (الدفع) حسب تسعيره ثابته للنزول بالوزن، لكل كيلو، حيث أن الزبون يدفع فقط عن كل وزن خسره (تم انقاصه)، حيث تم تحديد سعر 100 شيكل لكل كيلو ينقصها الزبون
اكتب عملية تصدر تقريرا فيه تُعيد هذه العملية جملة نصية فيها تفاصيل الزبون.
(ب)
اكتب برنامج يُعرف زبونين ويُنشئهما، ويتتبع برنامجهما لمدة ثلاثة أسابيع. على البرنامج أن يطبع في النهاية تفاصيل هذين الزبونين وكم على كل واحد منهما أن يدفع.
(أ)
class Client
{
private string name;
private string id;
private double firstWeight; //Taken from the constructor. Without set can`t be change.
private double weight;
private double tall;
private int charge; //Without set because it calculated.
// Constructor that build the object with: name,id,first weight and the tall
public Client(string name, string id, double firstWeight, double tall)
{
this.name = name;
this.id = id;
this.firstWeight = firstWeight;
this.tall = tall;
}
public void setName(string name)
{
this.name = name;
}
public void setId(string id)
{
this.id = id;
}
public void setWeight(double weight)
{
this.weight = weight;
}
public void setTall(double tall)
{
this.tall = tall;
}
public string getName()
{
return this.name;
}
public string GetId()
{
return this.id;
}
public double getFirstWeight()
{
return this.firstWeight;
}
public double getWeight()
{
return this.weight;
}
public double getTall()
{
return this.tall;
}
public int getCharge()
{
return this.charge;
}
public void clacMoney() //its don`t return any value, just update the charge.
{
const int PRICE_PER_KILO = 100;
double money;
money = (this.firstWeight - this.weight) * PRICE_PER_KILO;
this.charge=(int)money; //casting to int
}
public string report()
{
return this.name+"[id:"+this.id+"]--> F.W="+this.firstWeight+" ,L.W="+this.weight+" Charge="+this.charge;
}
}(ب)
static void Main(string[] args)
{
const int NUMBER_OF_WEEKS = 3;
const int NUMBER_OF_CLIENTS = 2;
int i,j;
string name, id;
double weight, tall;
double money=0;
Console.WriteLine("******** First Person ********");
Console.Write("Enter the name:");
name = Console.ReadLine();
Console.Write("Enter the id: ");
id = Console.ReadLine();
Console.Write("Enter your first weight: ");
weight = double.Parse(Console.ReadLine());
Console.Write("Enter your tall: ");
tall = double.Parse(Console.ReadLine());
Client p1 = new Client(name,id,weight,tall);
Console.WriteLine("******** second Person ********");
Console.Write("Enter the name:");
name = Console.ReadLine();
Console.Write("Enter the id: ");
id = Console.ReadLine();
Console.Write("Enter your first weight: ");
weight = double.Parse(Console.ReadLine());
Console.Write("Enter your tall: ");
tall = double.Parse(Console.ReadLine());
Client p2 = new Client(name, id, weight, tall);
for (j = 0; j < NUMBER_OF_CLIENTS; j++)
{
Console.WriteLine("Inserting data about {0} client",j+1);
for (i = 0; i < NUMBER_OF_WEEKS; i++)
{
Console.Write("Hi, Week {0}. insert your weight: ",i+1);
weight = double.Parse(Console.ReadLine());
if (j == 0)
{
p1.setWeight(weight);
p1.clacMoney();
}
else
{
p2.setWeight(weight);
p2.clacMoney();
}
}
}
Console.WriteLine(p1.report());
Console.WriteLine(p2.report());
}(أ) اكتب فئة ساعة Clock فيها الصفات التالية:
- الساعة h رقم صحيح من 0 حتى 23
- الدقيقة m رقم صحيح من 0 حتى 59
(ب) ابنِ عمليات وصول set و get للصفات أعلاه.
(ج) ابن العمليات التالية:
- prnt عملية لا تأخذ بارامترات ولا تعيد بارامترات انما تطبع على الشاشة الساعة .
- tik عملية تقدم الساعة بقيمة دقيقة (تتحرك الى الأمام دقيقة).
(د) اكتب برنامج رئيسي فيه يتم انشاء ساعة تبدأ من التوقيت التالي: 14:30 وتتحرك الدقائق للأمام بواسطة استخدام العملية tik وذلك كل برهة ثابتة من الزمن (وكأن الساعة تتقدم) ونطبع على الشاشة الساعة.
استخدم الأمر Console.Clear(); كي يتم محو الشاشة للطباعة من جديد.
using System;
namespace clock1
{
class Clock
{
private int h;
private int m;
public void sethour(int h)
{
this.h = h;
}
public void setmin(int m)
{
this.m = m;
}
public int gethour()
{
return this.h;
}
public int getmin()
{
return this.m;
}
public void prnt()
{
Console.WriteLine(this.h+":"+this.m);
}
public void tik()
{
if (this.m == 59)
{
this.m = 0;
if (this.h == 23)
this.h = 0;
else
this.h++;
}
else
this.m++;
}
}
class Program
{
static void Main(string[] args)
{
Clock t1 = new Clock();
t1.sethour(14);
t1.setmin(30);
while(true)
{
t1.prnt();
for(int i=0; i<90000000; i++)
{ }
t1.tik();
Console.Clear();
}
}
}
}