تعلم سي شارب تطبيق البرمجة كائنية التوجه في لغة سي شارب #C - الجزء الثاني


حسام برهان

تحدثنا في الدرس السابق عن المبادئ الأوليّة لتطبيق مفاهيم البرمجة كائنيّة التوجّه في سي شارب، حيث تعلّمنا كيفيّة إنشاء الأصناف وأعضائها، وإنشاء الكائنات من الأصناف والتعامل معها.

سنتابع في هذا الدرس الحديث عن تطبيق مبادئ البرمجة كائنيّة التوجّه في سي شارب حيث سنتناول مُحدّد الوصول private (تناولنا محدّد الوصول public في الدرس السابق). وسنتحدّث أيضًا عن الخصائص Properties كنوع جديد من أعضاء الصنف، والفرق بينها وبين حقول البيانات Data Fields. وسنختم هذا الدرس بالحديث عن الأعضاء الساكنة Static Members في الصنف.

learn-csharp-oop.png

الخصائص Properties

للخصائص ولحقول البيانات المعنى المنطقيّ نفسه في البرمجة كائنيّة التوجّه، إلّا أنّ سي شارب تميّز بينهما من الناحية العمليّة. للخصائص في سي شارب مرونة أكبر، حيث من الممكن تنفيذ عبارات برمجيّة عند إسناد قيمة إلى خاصيّة أو حتى عند القراءة منها. كما من الممكن أن نجعل إحدى الخواص قابلة للقراءة فقط أو حتى قابلة للكتابة فقط (ولو أنّه أمر نادر الحدوث). لفهم هذه المزايا بشكل جيّد سأستعير البرنامج Lesson06_02 من الدرس السابق، وأجري عليه بعض التعديلات لإدخال مفهوم الخصائص.

1   using System;
2
3   namespace Lesson07_01
4   {
5
6       class Employee
7       {
8           private string firstName;
9           private string lastName;
10          private double salary;
11
12          public string FirstName
13          {
14              get
15              {
16                  return this.firstName;
17              }
18              set
19              {
20                  this.firstName = value;
21              }
22          }
23 
24          public string LastName
25          {
26              get
17              {
28                  return this.lastName;
29              }
30              set
31              {
32                  this.lastName = value;
33              }
34          }
35  
36
37          public double Salary
38          {
39              get
40              {
41                  return this.salary;
42              }
43              set
44              {
45                  this.salary = value;
46              }
47          }
48
49          public string DisplayInfo()
50          {
51              string result = string.Format("{0} {1} - Salary: {2:N0}",
52                  this.FirstName, this.LastName, this.Salary);
53
54              return result;
55          }
56
57          public Employee(string firstName, string lastName, double salary)
58          {
59              this.FirstName = firstName;
60              this.LastName = lastName;
61              this.Salary = salary;
62          }
63
64          public Employee()
65          {
66
67          }
68      }
69
70
71      class Program
72      {
73          static void Main(string[] args)
74          {
75              Employee employee1, employee2;
76
77              employee1 = new Employee("Mohammad", "Mansoor", 1000);
78              employee2 = new Employee("Saleh", "Mahmoud", 2500);
79
80              Console.WriteLine("First Employee: {0}", employee1.DisplayInfo());
81              Console.WriteLine("Second Employee: {0}", employee2.DisplayInfo());
82          }
83      }
84  }

عند تنفيذ البرنامج Lesson07_01 سنحصل على نفس الخرج الذي حصلنا عليه في البرنامج Lesson06_02. لقد أجرينا في الحقيقة بعض التعديلات التي تبدو للوهلة الأولى أنّها ليست ذات مغزى. لقد استبدلنا محدّد الوصول للحقول في الأسطر من 8 حتى 10 ليصبح private بدلًا من public (كما كان الوضع في البرنامج Lesson06_02). يفيد مُحدّد الوصول هذا في جعل الحقل خاصًّا بالصنف ولا يمكن الوصول إليه من خارج الكائن المُنشَأ من هذا الصنف، وبالتالي لا يمكن لأحد أن يُعدّل عليه إلّا التوابع الموجودة ضمن نفس الصنف حصرًا. الأمر الآخر أنّنا قد جعلنا أسماء الحقول تبدأ بحرف طباعي صغير وذلك لتمييزها عن الخصائص التي ستأتي بعدها والتي تحمل نفس الاسم ولكن بحرف طباعي كبير.

يبدأ التصريح عن الخاصيّة FirstName في السطر 12 ويمتدّ حتى السطر 22. للخصائص في سي شارب فوائد عظيمة سنختبرها بالتدريج في هذا الدرس وفي الدروس اللّاحقة. لاحظ وجود النوع string قبل اسم الخاصيّة في السطر 12، يُشير ذلك إلى أنّ هذه الخاصيّة تقبل وتعطي قيمًا نصيّة فحسب. في الحقيقة يمكن استبدال string بأيّ نوع نحتاجه. من الواضح أنّ التصريح عن الخاصيّة FirstName يتألّف من قسمين: قسم القراءة get (بين السطرين 14 و 17) وقسم الإسناد set (بين السطرين 18 و 21). في الواقع لا تتعدّى الخاصيّة كونها وسيلة للوصول إلى الحقول الخاصّة private fields الموجودة ضمن الكائن سواءً بالقراءة أو الإسناد، ولكن مع إمكانيّة معالجة القيم سواءً قبل إسنادها إلى هذه الحقول أو بعد القراءة منها. عندما نحاول إسناد قيمة إلى الخاصيّة FirstName ستُنفّذ العبارات البرمجيّة الموجودة في قسم set، وهي عبارة برمجيّة واحدة فقط في مثالنا هذا:

this.firstName = value;

الكلمة value هي كلمة محجوزة تحتوي على القيمة المُسندة إلى الخاصيّة FirstName. العبارة السابقة واضحة للغاية فهي تعمل على إسناد القيمة المخزّنة ضمن value إلى الحقل firstName. لاحظ كيف يمكننا الوصول إلى هذا الحقل من الكلمة this، كما ويمكننا إغفالها. نستطيع الوصول إلى الحقل firstName رغم أنّه ذو محدّد وصول private لأنّنا نصل إليه من تابع يقع في نفس الصنف.

أمّا عندما نحاول قراءة الخاصيّة FirstName فسيتمّ تنفيذ العبارات البرمجيّة الموجودة ضمن القسم get. في هذا المثال يحتوي القسم get على عبارة برمجيّة واحدة وهي:

return this.firstName;

حيث تعمل على إرجاع القيمة المخزّنة ضمن الحقل firstName إلى الشيفرة التي طلبت قراءة الخاصيّة FirstName. يطبّق نفس الأمر تمامًا على الخاصيّتين LastName و Salary.

الخصائص المطبقة تلقائيا

يبدو البرنامج السابق طويلًا بلا مبرّر، فنحن لم نقم بأيّ عمل ضمن الخصائص سوى الإسناد أو القراءة. إذا كان الأمر كذلك في برامجك الحقيقيّة فيمكنك الاستغناء عن هذا الشكل من الخصائص واللجوء إلى شكل أكثر حداثةً وعصريّة، والذي يتمثّل بالخصائص المطبّقة تلقائيًّا auto implemented properties. انظر الشكل العام لها فيما يتعلّق بالخاصيّة FirstName:

public string FirstName
{
  get;
  set;
}

لم تعد العبارات البرمجيّة في قسميّ get و set موجودة. ينحصر دور هذه الخاصيّة في شكلها الحالي في تخزين القيم ضمن الخاصيّة FirstName والقراءة منها فقط. ولكن يأتي السؤال هنا، أين ستخزّن الخاصيّة FirstName قيمها، فأنا لا أرى حقلًا للتخزين!

يعمل المترجم في هذه الحالة على إنشاء حقل خاص غير مُشاهد في شيفرة MSIL وظيفته الاحتفاظ بقيمة الخاصيّة FirstName. وهنا قد يجول بخاطرك سؤال آخر: لماذا كلّ هذا التعقيد، لماذا لا نستخدم الحقول كما كنّا نفعل في الدرس السابق وحسب؟

الإجابة بسيطة على هذا التساؤل المشروع. فنحن نستخدم الخصائص بهذا الشكل لغايات تصميميّة فحسب. فكلّما كنت بحاجة لأن تُضيف خاصيّة لأحد الأصناف يمكن الوصول إليها من خارجه فافعل ذلك عن طريق الخصائص (وليس الحقول) ولن تندم، وإن بدا ذلك يتطلّب المزيد من العمل.

سنطبّق هذه الخصائص الفريدة على برنامجنا المعدّل Lesson07_02 بحيث تستغني تمامًا عن الحقول firstName و lastName و salary.

1   using System;
2
3   namespace Lesson07_02
4   {
5
6       class Employee
7       {
8           public string FirstName { get; set; }
9           public string LastName { get; set; }
10          public double Salary { get; set; }
11
12          public string DisplayInfo()
13          {
14              string result = string.Format("{0} {1} - Salary: {2:N0}",
15                  this.FirstName, this.LastName, this.Salary);
16
17              return result;
18          }
19
20          public Employee(string firstName, string lastName, double salary)
21          {
22              this.FirstName = firstName;
23              this.LastName = lastName;
24              this.Salary = salary;
25          }
26
27          public Employee()
28          {
29
30          }
31      }
32
33
34      class Program
35      {
36          static void Main(string[] args)
37          {
38              Employee employee1, employee2;
39
40              employee1 = new Employee("Mohammad", "Mansoor", 1000);
41              employee2 = new Employee("Saleh", "Mahmoud", 2500);
42
43              Console.WriteLine("First Employee: {0}", employee1.DisplayInfo());
44              Console.WriteLine("Second Employee: {0}", employee2.DisplayInfo());
45          }
46      }
47  }

أصبح هذا البرنامج الآن يُشبه البرنامج Lesson06_02 من الدرس السابق إلى حدّ كبير، باستثناء أنّنا نستخدم هنا الخصائص بدلًا من الحقول. لاحظ فقط أنّه يمكننا كتابة التصريح عن أيّ خاصيّة على نفس السطر مثل الأسطر 8 و 9 و 10.

الخصائص ذات إمكانية القراءة فقط

هل تذكُر التمرين الداعم الأوّل من الدرس السابق؟

كان يطلب ذلك التمرين إضافة تابع جديد اسمه GetSalaryAfterTax للحصول على قيمة الراتب بعد خصم الضريبة. واتفقنا وقتها أن تكون هذه الضريبة 2%. سنضيف خاصيّةً لتقوم بهذه المهمّة بدلًا من هذا التابع، ولكنّنا سنجعلها للقراءة فقط read only. أي لا يمكن إسناد أي قيم لها.

ستكون الخاصيّة SalaryAfterTax الجديدة على الشكل التالي:

public double SalaryAfterTax
{
  get
  {
    return 0.98 * this.Salary;
  }
}

من الواضح أنّه قد أزلنا القسم set المسؤول عن الإسناد من تصريح الخاصيّة SalaryAfterTax وبذلك تتحوّل للقراءة فقط. يحتوي القسم get على عمليّة حسابيّة بسيطة تطبّق عملية حسم الضريبة على الراتب Salary.

سنجري تعديلًا طفيفًا على التابع DisplayInfo لكي يُرفق قيمة الراتب بعد حسم الضريبة ضمن النصّ المنسّق الذي يرجعه. سنحصل في النتيجة على البرنامج Lesson07_03 المعدّل:

1   using System;
2
3   namespace Lesson07_03
4   {
5
6       class Employee
7       {
8           public string FirstName { get; set; }
9           public string LastName { get; set; }
10          public double Salary { get; set; }
11          public double SalaryAfterTax
12          {
13              get
14              {
15                  return 0.98 * this.Salary;
16              }
17          }
18         
19          public string DisplayInfo()
20          {
21              string result = string.Format("{0} {1} \n Salary: {2:N0} \n Salary after tax: {3:N0}",
22                  this.FirstName, this.LastName, this.Salary, this.SalaryAfterTax);
23
24              return result;
25          }
26
27          public Employee(string firstName, string lastName, double salary)
28          {
29              this.FirstName = firstName;
30              this.LastName = lastName;
31              this.Salary = salary;
32          }
33
34          public Employee()
35          {
36
37          }
38      }
39
40
41      class Program
42      {
43          static void Main(string[] args)
44          {
45              Employee employee1, employee2;
46
47              employee1 = new Employee("Mohammad", "Mansoor", 1000);
48              employee2 = new Employee("Saleh", "Mahmoud", 2500);
49
50              Console.WriteLine("First Employee: {0}", employee1.DisplayInfo());
51              Console.WriteLine("Second Employee: {0}", employee2.DisplayInfo());
52          }
53      }
54  }

أضفت الخاصيّة SalaryAfterTax (الأسطر من 11 حتى 17). وأجريت تعديلًا طفيفًا ضمن التابع DisplayInfo في السطر 21 حيث أضفت المحرف n\ والذي يُستخدم ضمن النص للإشارة إلى وجوب الانتقال إلى سطر جديد لأغراض تنسيقية فقط، كما أضفت مكانًا في النصّ التنسيقيّ {3:N0} لإدراج قيمة الراتب بعد خصم الضريبة this.SalaryAfterTax، وهذا كلّ ما في الأمر.

إذا حاولت في هذا البرنامج أن تُسند أيّ قيمة إلى الخاصيّة SalaryAfterTax ستحصل على خطأ يفيد أنّها للقراءة فقط read only.

الأعضاء الساكنة Static Members

هي أعضاء يمكن استدعاؤها مباشرةً من الصنف الذي صُرّحت ضمنه، وليس من كائن مُنشَأ من هذا الصنف. يمكن أن نجعل أيّ عضو ساكن وذلك بوسمه بالكلمة المحجوزة static. يوضّح البرنامج Lesson07_04 استخدام التوابع الساكنة. لاحظ وجود الكلمة المحجوزة static بعد محّدد الوصول public:

1   using System;
2
3   namespace Lesson07_04
4   {
5       class Calculator
6       {
7           public static double Addition(double x, double y)
8           {
9               return x + y;
10          }
11
12          public static double Minus(double x, double y)
13          {
14              return x - y;
15          }
16
17          public static double Division(double x, double y)
18          {
19              if (y == 0)
20              {
21                  return double.NaN;
22              }
23              else
24              {
25                  return x / y;
26              }
27          }
28 
29          public static double Multiplication(double x, double y)
30          {
31              return x * y;
32          }
33      }
34
35      class Program
36      {
37          static void Main(string[] args)
38          {
39              double x = 5;
40              double y = 9;
41
42              double addition = Calculator.Addition(x, y);
43              double minus = Calculator.Minus(x, y);
44              double multiplication = Calculator.Multiplication(x, y);
45              double division = Calculator.Division(x, y);
46
47              Console.WriteLine("{0} + {1} = {2}", x, y, addition);
48              Console.WriteLine("{0} - {1} = {2}", x, y, minus);
49              Console.WriteLine("{0} * {1} = {2}", x, y, multiplication);
50              Console.WriteLine("{0} / {1} = {2}", x, y, division);
51          }
52      }
53  }

نفّذ البرنامج السابق لتحصل على الخرج التالي:

5 + 9 = 14
5 - 9 = -4
5 * 9 = 45
5 / 9 = 0.555555555555556

أنشأنا الصنف Calculator الذي يحتوي على التوابع الساكنة Addition و Minus و Multiplication و Division. إذا انتقلنا إلى التابع Main (الذي هو بالمناسبة تابع ساكن بسبب وجود الكلمة static) انظر إلى السطر 42 كيف استدعينا التابع Addition من الصنف Calculator مباشرةً بدون إنشاء أي كائن من هذا الصنف. سنكرّر نفس العمليّة من أجل التوابع Minus و Multiplication و Division.

أمرٌ أخير. انظر إلى محتوى التابع الساكن Division، ستجد أنّنا نختبر قيمة y فيما إذا كانت تساوي الصفر أم لا. فإذا كانت قيمة y تساوي الصفر فإنّه لا يجوز القسمة على صفر. لذلك فنرجع double.NaN وهو عبارة عن ثابت يُعبّر عن عدم وجود قيمة عدديّة. وهذا أمر طبيعي لأنّ القسمة على صفر لن تعطينا عدد. إذا استبدلت قيمة y في السطر 40 من البرنامج السابق بالقيمة 0 سنحصل على الخرج التالي:

5 + 0 = 5
5 - 0 = 5
5 * 0 = 0
5 / 0 = NaN

لاحظ السطر الأخير من البرنامج كيف يبدو منطقيًّا تمامًا.

يمكننا تعميم نفس المفهوم السابق بالنسبة للخصائص والحقول ضمن الصنف بجعلها ساكنة وذلك بإضافة الكلمة المحجوزة static بعد محدّد الوصول مباشرةً.

بقي أن نشير إلى أنّه من غير الممكن استخدام الكلمة المحجوزة this ضمن أي عضو ساكن والسبب كما أعتقد واضح. يُشير this إلى الكائن الذي يحدث من ضمنه الاستدعاء. ولكن في الأعضاء الساكنة فإنّنا نجري الاستدعاءات للتوابع أو الخصائص من الصنف المصرّحة ضمنه مباشرةً، لذلك فاستخدام this لن يكون له أيّ معنى.

تمارين داعمة

تمرين 1

أجرِ تعديلًا على البرنامج Lesson07_03 بحيث يُضيف الخاصيّة Tel (قابلة للقراءة وللكتابة) التي تمثّل رقم الهاتف للموظّف Employee، ثمّ أجرِ التعديل المناسب على التابع DisplayInfo لكي يعرض قيمة هذه الخاصيّة على سطر منفصل كما يفعل مع بقيّة الخصائص.

تمرين 2

أنشئ صنفًا سمّه StaticDemo بحيث يحتوي على خاصيّة ساكنة اسمها Counter من النوع int. عند كل إنشاء لكائن من هذا الصنف يجب زيادة قيمة هذه الخاصيّة بمقدار 1 وبشكل تلقائيّ.

(تلميح: يمكنك كتابة العبارة البرمجيّة المسؤولة عن زيادة قيمة الخاصيّة Counter ضمن بانيّة الصنف StaticDemo التي ليس لها وسائط.)

الخلاصة

تعرّفنا في هذا الدرس على كيفيّة التعامل مع الخصائص، والتي تمثّل أسلوبًا أكثر تطوّرًا من الحقول لتشكّل مزايا الصنف. كما اتفقنا أنّه ينبغي على الحقول أن تكون داخليّة بالنسبة للصنف باستخدام محدّد الوصول private، وتعرّفنا أيضًا على الأعضاء الساكنة static members وكيفيّة التعامل معها.



2 اشخاص أعجبوا بهذا


تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن